// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

/// Contains logic to coordinate assembly resolution and manage the TcImports table of referenced
/// assemblies.
module internal FSharp.Compiler.CompilerImports

open System
open System.Collections.Generic
open System.Diagnostics
open System.IO
#if !FABLE_COMPILER
open System.IO.Compression
#endif
open System.Reflection

open Internal.Utilities
open Internal.Utilities.Collections
#if !FABLE_COMPILER
open Internal.Utilities.FSharpEnvironment
#endif
open Internal.Utilities.Library
open Internal.Utilities.Library.Extras

open FSharp.Compiler
open FSharp.Compiler.AbstractIL.IL
open FSharp.Compiler.AbstractIL.ILBinaryReader
open FSharp.Compiler.AbstractIL.Diagnostics
open FSharp.Compiler.CheckDeclarations
open FSharp.Compiler.CompilerGlobalState
open FSharp.Compiler.CompilerConfig
#if !FABLE_COMPILER
open FSharp.Compiler.DependencyManager
#endif
open FSharp.Compiler.DiagnosticsLogger
open FSharp.Compiler.Import
open FSharp.Compiler.IO
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Syntax.PrettyNaming
open FSharp.Compiler.SyntaxTreeOps
open FSharp.Compiler.TcGlobals
open FSharp.Compiler.Text
open FSharp.Compiler.Text.Range
open FSharp.Compiler.Xml
open FSharp.Compiler.TypedTreePickle
open FSharp.Compiler.TypedTree
open FSharp.Compiler.TypedTreeBasics
open FSharp.Compiler.TypedTreeOps
#if !NO_TYPEPROVIDERS
open FSharp.Compiler.TypeProviders
open FSharp.Core.CompilerServices
#endif

let (++) x s = x @ [ s ]

//----------------------------------------------------------------------------
// Signature and optimization data blobs
//--------------------------------------------------------------------------

let IsSignatureDataResource (r: ILResource) =
    r.Name.StartsWithOrdinal FSharpSignatureDataResourceName
    || r.Name.StartsWithOrdinal FSharpSignatureCompressedDataResourceName
    || r.Name.StartsWithOrdinal FSharpSignatureDataResourceName2

let IsSignatureDataResourceB (r: ILResource) =
    r.Name.StartsWithOrdinal FSharpSignatureDataResourceNameB
    || r.Name.StartsWithOrdinal FSharpSignatureCompressedDataResourceNameB

let IsOptimizationDataResource (r: ILResource) =
    r.Name.StartsWithOrdinal FSharpOptimizationDataResourceName
    || r.Name.StartsWithOrdinal FSharpOptimizationCompressedDataResourceName
    || r.Name.StartsWithOrdinal FSharpOptimizationDataResourceName2

let IsOptimizationDataResourceB (r: ILResource) =
    r.Name.StartsWithOrdinal FSharpOptimizationDataResourceNameB
    || r.Name.StartsWithOrdinal FSharpOptimizationCompressedDataResourceNameB

let decompressResource (r: ILResource) =
#if FABLE_COMPILER
    r.GetBytes() // no support for gunzip
#else
    use raw = r.GetBytes().AsStream()
    use decompressed = new MemoryStream()
    use deflator = new DeflateStream(raw, CompressionMode.Decompress)
    deflator.CopyTo decompressed
    deflator.Close()
    ByteStorage.FromByteArray(decompressed.ToArray()).GetByteMemory()
#endif

let GetSignatureDataResourceName (r: ILResource) =
    if r.Name.StartsWithOrdinal FSharpSignatureDataResourceName then
        (fun () -> r.GetBytes()), String.dropPrefix r.Name FSharpSignatureDataResourceName
    elif r.Name.StartsWithOrdinal FSharpSignatureCompressedDataResourceName then
        (fun () -> decompressResource r), String.dropPrefix r.Name FSharpSignatureCompressedDataResourceName
    elif r.Name.StartsWithOrdinal FSharpSignatureDataResourceNameB then
        (fun () -> r.GetBytes()), String.dropPrefix r.Name FSharpSignatureDataResourceNameB
    elif r.Name.StartsWithOrdinal FSharpSignatureCompressedDataResourceNameB then
        (fun () -> decompressResource r), String.dropPrefix r.Name FSharpSignatureCompressedDataResourceNameB
    elif r.Name.StartsWithOrdinal FSharpSignatureDataResourceName2 then
        (fun () -> r.GetBytes()), String.dropPrefix r.Name FSharpSignatureDataResourceName2
    else
        failwith "unreachable"

let GetResourceNameAndSignatureDataFuncs (resources: ILResource list) =
    [
        for r in resources do
            if IsSignatureDataResource r then
                let readerA, ccuName = GetSignatureDataResourceName r

                let readerB =
                    resources
                    |> List.tryPick (fun rB ->
                        if IsSignatureDataResourceB rB then
                            let readerB, ccuNameB = GetSignatureDataResourceName rB
                            if ccuName = ccuNameB then Some readerB else None
                        else
                            None)

                ccuName, (readerA, readerB)
    ]

let GetOptimizationDataResourceName (r: ILResource) =
    if r.Name.StartsWithOrdinal FSharpOptimizationDataResourceName then
        (fun () -> r.GetBytes()), String.dropPrefix r.Name FSharpOptimizationDataResourceName
    elif r.Name.StartsWithOrdinal FSharpOptimizationCompressedDataResourceName then
        (fun () -> decompressResource r), String.dropPrefix r.Name FSharpOptimizationCompressedDataResourceName
    elif r.Name.StartsWithOrdinal FSharpOptimizationDataResourceNameB then
        (fun () -> r.GetBytes()), String.dropPrefix r.Name FSharpOptimizationDataResourceNameB
    elif r.Name.StartsWithOrdinal FSharpOptimizationCompressedDataResourceNameB then
        (fun () -> decompressResource r), String.dropPrefix r.Name FSharpOptimizationCompressedDataResourceNameB
    elif r.Name.StartsWithOrdinal FSharpOptimizationDataResourceName2 then
        (fun () -> r.GetBytes()), String.dropPrefix r.Name FSharpOptimizationDataResourceName2
    else
        failwith $"GetOptimizationDataResourceName - {r.Name}"

let GetResourceNameAndOptimizationDataFuncs (resources: ILResource list) =
    [
        for r in resources do
            if IsOptimizationDataResource r then
                let readerA, ccuName = GetOptimizationDataResourceName r

                let readerB =
                    resources
                    |> List.tryPick (fun rB ->
                        if IsOptimizationDataResourceB rB then
                            let readerB, ccuNameB = GetOptimizationDataResourceName rB
                            if ccuName = ccuNameB then Some readerB else None
                        else
                            None)

                ccuName, (readerA, readerB)
    ]

let IsReflectedDefinitionsResource (r: ILResource) =
    r.Name.StartsWithOrdinal(QuotationPickler.SerializedReflectedDefinitionsResourceNameBase)

#if !FABLE_COMPILER

let ByteBufferToBytes compress (bytes: ByteBuffer) =
    if compress then
        let raw = new MemoryStream(bytes.AsMemory().ToArray())
        let compressed = new MemoryStream()
        use deflator = new DeflateStream(compressed, CompressionLevel.Optimal)
        raw.CopyTo deflator
        deflator.Close()
        compressed.ToArray()
    else
        bytes.AsMemory().ToArray()

let PickleToResource inMem file (g: TcGlobals) compress scope rName rNameB p x =
    let file = PathMap.apply g.pathMap file

    let bytes, bytesB = pickleObjWithDanglingCcus inMem file g scope p x
    use bytes = bytes
    use bytesB = bytesB
    let bytes = ByteBufferToBytes compress bytes
    let bytesB = ByteBufferToBytes compress bytesB
    let byteStorage = ByteStorage.FromByteArray(bytes)

    let byteStorageB =
        if inMem then
            ByteStorage.FromMemoryAndCopy(bytesB.AsMemory(), useBackingMemoryMappedFile = true)
        else
            ByteStorage.FromByteArray(bytesB.AsMemory().ToArray())

    let resource =
        {
            Name = rName
            Location = ILResourceLocation.Local(byteStorage)
            Access = ILResourceAccess.Public
            CustomAttrsStored = storeILCustomAttrs emptyILCustomAttrs
            MetadataIndex = NoMetadataIdx
        }

    let resourceB =
        if bytesB.AsMemory().Length > 0 then
            Some
                {
                    Name = rNameB
                    Location = ILResourceLocation.Local(byteStorageB)
                    Access = ILResourceAccess.Public
                    CustomAttrsStored = storeILCustomAttrs emptyILCustomAttrs
                    MetadataIndex = NoMetadataIdx
                }
        else
            None

    resource, resourceB

let GetSignatureData (file, ilScopeRef, ilModule, byteReaderA, byteReaderB) : PickledDataWithReferences<PickledCcuInfo> =
    let memA = byteReaderA ()

    let memB =
        (match byteReaderB with
         | None -> ByteMemory.Empty.AsReadOnly()
         | Some br -> br ())

    unpickleObjWithDanglingCcus file ilScopeRef ilModule unpickleCcuInfo memA memB

let WriteSignatureData (tcConfig: TcConfig, tcGlobals, exportRemapping, ccu: CcuThunk, fileName, inMem) =
    let mspec = ApplyExportRemappingToEntity tcGlobals exportRemapping ccu.Contents

    if tcConfig.dumpSignatureData then
        tcConfig.outputFile
        |> Option.iter (fun outputFile ->
            let outputFile = FileSystem.GetFullPathShim(outputFile)

            let signatureDataFile =
                FileSystem.ChangeExtensionShim(outputFile, ".signature-data.json")

            serializeEntity signatureDataFile mspec)

    // For historical reasons, we use a different resource name for FSharp.Core, so older F# compilers
    // don't complain when they see the resource.
    let rName =
        if tcConfig.compressMetadata then
            FSharpSignatureCompressedDataResourceName
        elif ccu.AssemblyName = getFSharpCoreLibraryName then
            FSharpSignatureDataResourceName2
        else
            FSharpSignatureDataResourceName

    let rNameB =
        if tcConfig.compressMetadata then
            FSharpSignatureCompressedDataResourceNameB
        else
            FSharpSignatureDataResourceNameB

    let includeDir =
        if String.IsNullOrEmpty tcConfig.implicitIncludeDir then
            ""
        else
            tcConfig.implicitIncludeDir
            |> FileSystem.GetFullPathShim
            |> PathMap.applyDir tcGlobals.pathMap

    PickleToResource
        inMem
        fileName
        tcGlobals
        tcConfig.compressMetadata
        ccu
        (rName + ccu.AssemblyName)
        (rNameB + ccu.AssemblyName)
        pickleCcuInfo
        {
            mspec = mspec
            compileTimeWorkingDir = includeDir
            usesQuotations = ccu.UsesFSharp20PlusQuotations
        }

let GetOptimizationData (file, ilScopeRef, ilModule, byteReaderA, byteReaderB) =
    let memA = byteReaderA ()

    let memB =
        (match byteReaderB with
         | None -> ByteMemory.Empty.AsReadOnly()
         | Some br -> br ())

    unpickleObjWithDanglingCcus file ilScopeRef ilModule Optimizer.u_CcuOptimizationInfo memA memB

let WriteOptimizationData (tcConfig: TcConfig, tcGlobals, fileName, inMem, ccu: CcuThunk, modulInfo) =
    // For historical reasons, we use a different resource name for FSharp.Core, so older F# compilers
    // don't complain when they see the resource.
    let rName =
        if tcConfig.compressMetadata then
            FSharpOptimizationCompressedDataResourceName
        elif ccu.AssemblyName = getFSharpCoreLibraryName then
            FSharpOptimizationDataResourceName2
        else
            FSharpOptimizationDataResourceName

    let rNameB =
        if tcConfig.compressMetadata then
            FSharpOptimizationCompressedDataResourceNameB
        else
            FSharpOptimizationDataResourceNameB

    PickleToResource
        inMem
        fileName
        tcGlobals
        tcConfig.compressMetadata
        ccu
        (rName + ccu.AssemblyName)
        (rNameB + ccu.AssemblyName)
        Optimizer.p_CcuOptimizationInfo
        modulInfo

let EncodeSignatureData (tcConfig: TcConfig, tcGlobals, exportRemapping, generatedCcu, outfile, isIncrementalBuild) =
    if tcConfig.GenerateSignatureData then
        let resource1, resource2 =
            WriteSignatureData(tcConfig, tcGlobals, exportRemapping, generatedCcu, outfile, isIncrementalBuild)

        let resources =
            [
                resource1
                match resource2 with
                | None -> ()
                | Some r -> r
            ]

        let sigAttr =
            mkSignatureDataVersionAttr tcGlobals (parseILVersion FSharpBinaryMetadataFormatRevision)

        [ sigAttr ], resources
    else
        [], []

let EncodeOptimizationData (tcGlobals, tcConfig: TcConfig, outfile, exportRemapping, data, isIncrementalBuild) =
    if tcConfig.GenerateOptimizationData then
        let data = map2Of2 (Optimizer.RemapOptimizationInfo tcGlobals exportRemapping) data

        let ccu, optData =
            if tcConfig.onlyEssentialOptimizationData then
                map2Of2 Optimizer.AbstractOptimizationInfoToEssentials data
            else
                data

        let r1, r2 =
            WriteOptimizationData(tcConfig, tcGlobals, outfile, isIncrementalBuild, ccu, optData)

        let resources =
            [
                r1
                match r2 with
                | None -> ()
                | Some r -> r
            ]

        resources
    else
        []

#endif //!FABLE_COMPILER

exception AssemblyNotResolved of originalName: string * range: range

exception MSBuildReferenceResolutionWarning of message: string * warningCode: string * range: range

exception MSBuildReferenceResolutionError of message: string * warningCode: string * range: range

#if !FABLE_COMPILER

let OpenILBinary (fileName, reduceMemoryUsage, pdbDirPath, shadowCopyReferences, tryGetMetadataSnapshot) =
    let opts: ILReaderOptions =
        {
            metadataOnly = MetadataOnlyFlag.Yes
            reduceMemoryUsage = reduceMemoryUsage
            pdbDirPath = pdbDirPath
            tryGetMetadataSnapshot = tryGetMetadataSnapshot
        }

    let location =
        // In order to use memory mapped files on the shadow copied version of the Assembly, we `preload the assembly
        // We swallow all exceptions so that we do not change the exception contract of this API
        if shadowCopyReferences && not isRunningOnCoreClr then
            try
                Assembly.ReflectionOnlyLoadFrom(fileName).Location
            with _ ->
                fileName
        else
            fileName

    AssemblyReader.GetILModuleReader(location, opts)

#endif //!FABLE_COMPILER

[<RequireQualifiedAccess>]
type ResolveAssemblyReferenceMode =
    | Speculative
    | ReportErrors

#if !NO_TYPEPROVIDERS
type ResolvedExtensionReference = ResolvedExtensionReference of string * AssemblyReference list * Tainted<ITypeProvider> list
#endif

#if DEBUG
[<DebuggerDisplay("AssemblyResolution({resolvedPath})")>]
#endif
type AssemblyResolution =
    {
        /// The original reference to the assembly.
        originalReference: AssemblyReference

        /// Path to the resolvedFile
        resolvedPath: string

        /// Create the tooltip text for the assembly reference
        prepareToolTip: unit -> string

        /// Whether or not this is an installed system assembly (for example, System.dll)
        sysdir: bool

        /// Lazily populated ilAssemblyRef for this reference.
        mutable ilAssemblyRef: ILAssemblyRef option
    }

    override this.ToString() =
        sprintf "%s%s" (if this.sysdir then "[sys]" else "") this.resolvedPath

#if !FABLE_COMPILER

    member this.ProjectReference = this.originalReference.ProjectReference

    /// Compute the ILAssemblyRef for a resolved assembly. This is done by reading the binary if necessary. The result
    /// is cached.
    ///
    /// Only used in F# Interactive
    member this.GetILAssemblyRef(reduceMemoryUsage, tryGetMetadataSnapshot) =
        match this.ilAssemblyRef with
        | Some assemblyRef -> assemblyRef
        | None ->
            match this.ProjectReference with
            | Some _ -> failwith "IProjectReference is not allowed to be used in GetILAssemblyRef"
            | None -> ()

            let assemblyRef =
                let readerSettings: ILReaderOptions =
                    {
                        pdbDirPath = None
                        reduceMemoryUsage = reduceMemoryUsage
                        metadataOnly = MetadataOnlyFlag.Yes
                        tryGetMetadataSnapshot = tryGetMetadataSnapshot
                    }

                use reader = OpenILModuleReader this.resolvedPath readerSettings
                mkRefToILAssembly reader.ILModuleDef.ManifestOfAssembly

            this.ilAssemblyRef <- Some assemblyRef
            assemblyRef

#endif //!FABLE_COMPILER

type ImportedBinary =
    {
        FileName: string
        RawMetadata: IRawFSharpAssemblyData
#if !NO_TYPEPROVIDERS
        ProviderGeneratedAssembly: Assembly option
        IsProviderGenerated: bool
        ProviderGeneratedStaticLinkMap: ProvidedAssemblyStaticLinkingMap option
#endif
        ILAssemblyRefs: ILAssemblyRef list
        ILScopeRef: ILScopeRef
    }

type ImportedAssembly =
    {
        ILScopeRef: ILScopeRef
        FSharpViewOfMetadata: CcuThunk
        AssemblyAutoOpenAttributes: string list
        AssemblyInternalsVisibleToAttributes: string list
#if !NO_TYPEPROVIDERS
        IsProviderGenerated: bool
        mutable TypeProviders: Tainted<ITypeProvider> list
#endif
        FSharpOptimizationData: InterruptibleLazy<Optimizer.LazyModuleInfo option>
    }

type AvailableImportedAssembly =
    | ResolvedImportedAssembly of ImportedAssembly * range
    | UnresolvedImportedAssembly of string * range

type CcuLoadFailureAction =
    | RaiseError
    | ReturnNone

type TcImportsLockToken() =
    interface LockToken

#if !FABLE_COMPILER

type TcImportsLock = Lock<TcImportsLockToken>

let RequireTcImportsLock (_tcitok: TcImportsLockToken, _thingProtected: 'T) = ()

// if this is a #r reference (not from dummy range), make sure the directory of the declaring
// file is included in the search path. This should ideally already be one of the search paths, but
// during some global checks it won't be. We append to the end of the search list so that this is the last
// place that is checked.
let isHashRReference (r: range) =
    not (equals r range0)
    && not (equals r rangeStartup)
    && not (equals r rangeCmdArgs)
    && FileSystem.IsPathRootedShim r.FileName

let IsNetModule (fileName: string) =
    let ext = Path.GetExtension fileName
    String.Compare(ext, ".netmodule", StringComparison.OrdinalIgnoreCase) = 0

let IsDLL (fileName: string) =
    let ext = Path.GetExtension fileName
    String.Compare(ext, ".dll", StringComparison.OrdinalIgnoreCase) = 0

let IsExe (fileName: string) =
    let ext = Path.GetExtension fileName
    String.Compare(ext, ".exe", StringComparison.OrdinalIgnoreCase) = 0

let addConstraintSources (ia: ImportedAssembly) =
    let contents = ia.FSharpViewOfMetadata.Contents

    let addCxsToMember name (v: Val) =
        for typar in fst v.GeneralizedType do
            for cx in typar.Constraints do
                match cx with
                | TyparConstraint.MayResolveMember(TTrait(source = source), _) -> source.Value <- Some name
                | _ -> ()

    let rec addCxsToModule name (m: ModuleOrNamespaceType) =
        for e in m.ModuleAndNamespaceDefinitions do
            if e.IsModuleOrNamespace then
                let mname =
                    if String.length name > 0 then name + "." + e.DisplayName
                    elif e.IsModule then e.DisplayName
                    else ""

                addCxsToModule mname e.ModuleOrNamespaceType

        for memb in m.AllValsAndMembers do
            addCxsToMember (name + "." + memb.LogicalName) memb

    addCxsToModule "" contents.ModuleOrNamespaceType

type TcConfig with

    member tcConfig.TryResolveLibWithDirectories(r: AssemblyReference) =
        let m, nm = r.Range, r.Text
        use _ = UseBuildPhase BuildPhase.Parameter

        // See if the language service has already produced the contents of the assembly for us, virtually
        match r.ProjectReference with
        | Some _ ->
            let resolved = r.Text
            let sysdir = tcConfig.IsSystemAssembly resolved

            Some
                {
                    originalReference = r
                    resolvedPath = resolved
                    prepareToolTip = (fun () -> resolved)
                    sysdir = sysdir
                    ilAssemblyRef = None
                }
        | None ->

            // Only want to resolve certain extensions (otherwise, 'System.Xml' is ambiguous).
            // MSBuild resolution is limited to .exe and .dll so do the same here.
            if IsDLL nm || IsExe nm || IsNetModule nm then

                let searchPaths =
                    seq {
                        yield! tcConfig.GetSearchPathsForLibraryFiles()

                        if isHashRReference m then
                            !!Path.GetDirectoryName(m.FileName)
                    }

                let resolved = TryResolveFileUsingPaths(searchPaths, m, nm)

                match resolved with
                | Some resolved ->
                    let sysdir = tcConfig.IsSystemAssembly resolved

                    Some
                        {
                            originalReference = r
                            resolvedPath = resolved
                            prepareToolTip =
                                (fun () ->
                                    let fusionName = AssemblyName.GetAssemblyName(resolved).ToString()
                                    let line (append: string) = append.Trim(' ') + "\n"
                                    line resolved + line fusionName)
                            sysdir = sysdir
                            ilAssemblyRef = None
                        }
                | None -> None
            else
                None

    member tcConfig.ResolveLibWithDirectories(ccuLoadFailureAction, r: AssemblyReference) =
        let m, nm = r.Range, r.Text
        use _ = UseBuildPhase BuildPhase.Parameter

        let rs =
            if IsExe nm || IsDLL nm || IsNetModule nm then
                [ r ]
            else
                [
                    AssemblyReference(m, nm + ".dll", None)
                    AssemblyReference(m, nm + ".exe", None)
                    AssemblyReference(m, nm + ".netmodule", None)
                ]

        match rs |> List.tryPick (fun r -> tcConfig.TryResolveLibWithDirectories(r)) with
        | Some res -> Some res
        | None ->
            match ccuLoadFailureAction with
            | CcuLoadFailureAction.RaiseError ->
                let searchMessage = String.concat "\n " (tcConfig.GetSearchPathsForLibraryFiles())
                raise (FileNameNotResolved(nm, searchMessage, m))
            | CcuLoadFailureAction.ReturnNone -> None

    member tcConfig.MsBuildResolve(references, _, errorAndWarningRange, showMessages) =
        let logMessage showMessages =
            if showMessages && tcConfig.showReferenceResolutions then
                (fun (message: string) -> printfn "%s" message)
            else
                ignore

        let logDiagnostic _ =
            (fun (_: bool) (_: string) (_: string) -> ())

        let targetProcessorArchitecture =
            match tcConfig.platform with
            | None -> "MSIL"
            | Some X86 -> "x86"
            | Some AMD64 -> "amd64"
            | Some ARM -> "arm"
            | Some ARM64 -> "arm64"
            | Some IA64 -> "ia64"

        try
            tcConfig.legacyReferenceResolver.Impl.Resolve(
                tcConfig.resolutionEnvironment,
                references,
                tcConfig.targetFrameworkVersion,
                tcConfig.GetTargetFrameworkDirectories(),
                targetProcessorArchitecture,
                tcConfig.fsharpBinariesDir, // FSharp binaries directory
                tcConfig.includes, // Explicit include directories
                tcConfig.implicitIncludeDir, // Implicit include directory (likely the project directory)
                logMessage showMessages,
                logDiagnostic showMessages
            )
        with LegacyResolutionFailure ->
            error (Error(FSComp.SR.buildAssemblyResolutionFailed (), errorAndWarningRange))

    // NOTE!! if mode=Speculative then this method must not report ANY warnings or errors through 'warning' or 'error'. Instead
    // it must return warnings and errors as data
    //
    // NOTE!! if mode=ReportErrors then this method must not raise exceptions. It must just report the errors and recover
    static member TryResolveLibsUsingMSBuildRules
        (tcConfig: TcConfig, originalReferences: AssemblyReference list, errorAndWarningRange: range, mode: ResolveAssemblyReferenceMode)
        : AssemblyResolution list * UnresolvedAssemblyReference list =

        use _ = UseBuildPhase BuildPhase.Parameter

        if tcConfig.useSimpleResolution then
            failwith "MSBuild resolution is not supported."

        if List.isEmpty originalReferences then
            [], []
        else
            // Group references by name with range values in the grouped value list.
            // In the grouped reference, store the index of the last use of the reference.
            let groupedReferences =
                originalReferences
                |> List.indexed
                |> List.groupBy (fun (_, reference) -> reference.Text)
                |> List.map (fun (assemblyName, assemblyAndIndexGroup) ->
                    let assemblyAndIndexGroup = assemblyAndIndexGroup |> List.ofSeq
                    let highestPosition = assemblyAndIndexGroup |> List.maxBy fst |> fst
                    let assemblyGroup = assemblyAndIndexGroup |> List.map snd
                    assemblyName, highestPosition, assemblyGroup)
                |> Array.ofList

            // First, try to resolve everything as a file using simple resolution
            let resolvedAsFile =
                [|
                    for _filename, maxIndexOfReference, references in groupedReferences do
                        let assemblyResolution =
                            references |> List.choose tcConfig.TryResolveLibWithDirectories

                        if not assemblyResolution.IsEmpty then
                            (maxIndexOfReference, assemblyResolution)
                |]

            let toMsBuild =
                [|
                    for i in 0 .. groupedReferences.Length - 1 do
                        let ref, i0, _ = groupedReferences[i]

                        if resolvedAsFile |> Array.exists (fun (i1, _) -> i0 = i1) |> not then
                            ref, string i
                |]

            let resolutions =
                tcConfig.MsBuildResolve(toMsBuild, mode, errorAndWarningRange, true)

            // Map back to original assembly resolutions.
            let resolvedByMsbuild =
                [|
                    for resolvedFile in resolutions do
                        let i = int resolvedFile.baggage
                        let _, maxIndexOfReference, ms = groupedReferences[i]

                        let assemblyResolutions =
                            [
                                for originalReference in ms do
                                    let canonicalItemSpec = FileSystem.GetFullPathShim(resolvedFile.itemSpec)

                                    {
                                        originalReference = originalReference
                                        resolvedPath = canonicalItemSpec
                                        prepareToolTip = (fun () -> resolvedFile.prepareToolTip (originalReference.Text, canonicalItemSpec))
                                        sysdir = tcConfig.IsSystemAssembly canonicalItemSpec
                                        ilAssemblyRef = None
                                    }
                            ]

                        (maxIndexOfReference, assemblyResolutions)
                |]

            // When calculating the resulting resolutions, we're going to use the index of the reference
            // in the original specification and resort it to match the ordering that we had.
            let resultingResolutions =
                [ resolvedByMsbuild; resolvedAsFile ]
                |> Array.concat
                |> Array.sortBy fst
                |> Array.map snd
                |> List.ofArray
                |> List.concat

            // O(N^2) here over a small set of referenced assemblies.
            let IsResolved (originalName: string) =
                if
                    resultingResolutions
                    |> List.exists (fun resolution -> resolution.originalReference.Text = originalName)
                then
                    true
                else
                    // MSBuild resolution may have unified the result of two duplicate references. Try to re-resolve now.
                    // If re-resolution worked then this was a removed duplicate.
                    let references = [| (originalName, "") |]

                    let resolutions =
                        tcConfig.MsBuildResolve(references, mode, errorAndWarningRange, false)

                    resolutions.Length <> 0

            let unresolvedReferences =
                groupedReferences |> Array.filter (p13 >> IsResolved >> not) |> List.ofArray

            let unresolved =
                [
                    for name, _, r in unresolvedReferences -> UnresolvedAssemblyReference(name, r)
                ]

            // If mode=Speculative, then we haven't reported any errors.
            // We report the error condition by returning an empty list of resolutions
            if
                mode = ResolveAssemblyReferenceMode.Speculative
                && unresolvedReferences.Length > 0
            then
                [], unresolved
            else
                resultingResolutions, unresolved

[<Sealed>]
type TcAssemblyResolutions(tcConfig: TcConfig, results: AssemblyResolution list, unresolved: UnresolvedAssemblyReference list) =

    let originalReferenceToResolution =
        results |> List.map (fun r -> r.originalReference.Text, r) |> Map.ofList

    let resolvedPathToResolution =
        results |> List.map (fun r -> r.resolvedPath, r) |> Map.ofList

    /// Add some resolutions to the map of resolution results.
    member _.AddResolutionResults newResults =
        TcAssemblyResolutions(tcConfig, results @ newResults, unresolved)

    /// Add some unresolved results.
    member _.AddUnresolvedReferences newUnresolved =
        TcAssemblyResolutions(tcConfig, results, unresolved @ newUnresolved)

    /// Get information about referenced DLLs
    member _.GetAssemblyResolutions() = results

    member _.GetUnresolvedReferences() = unresolved

    member _.TryFindByOriginalReference(assemblyReference: AssemblyReference) =
        originalReferenceToResolution.TryFind assemblyReference.Text

    /// Only used by F# Interactive
    member _.TryFindByExactILAssemblyRef assemblyRef =
        results
        |> List.tryFind (fun ar ->
            let r =
                ar.GetILAssemblyRef(tcConfig.reduceMemoryUsage, tcConfig.tryGetMetadataSnapshot)

            r = assemblyRef)

    /// Only used by F# Interactive
    member _.TryFindBySimpleAssemblyName simpleAssemName =
        results
        |> List.tryFind (fun ar ->
            let r =
                ar.GetILAssemblyRef(tcConfig.reduceMemoryUsage, tcConfig.tryGetMetadataSnapshot)

            r.Name = simpleAssemName)

    member _.TryFindByResolvedPath nm = resolvedPathToResolution.TryFind nm

    member _.TryFindByOriginalReferenceText nm =
        originalReferenceToResolution.TryFind nm

    static member ResolveAssemblyReferences
        (tcConfig: TcConfig, assemblyList: AssemblyReference list, knownUnresolved: UnresolvedAssemblyReference list)
        : TcAssemblyResolutions =
        let resolved, unresolved =
            if tcConfig.useSimpleResolution then
                let resolutions =
                    assemblyList
                    |> List.map (fun assemblyReference ->
                        try
                            let resolutionOpt =
                                tcConfig.ResolveLibWithDirectories(CcuLoadFailureAction.RaiseError, assemblyReference)

                            Choice1Of2 resolutionOpt.Value
                        with RecoverableException e ->
                            errorRecovery e assemblyReference.Range
                            Choice2Of2 assemblyReference)

                let successes =
                    resolutions
                    |> List.choose (function
                        | Choice1Of2 x -> Some x
                        | _ -> None)

                let failures =
                    resolutions
                    |> List.choose (function
                        | Choice2Of2 x -> Some(UnresolvedAssemblyReference(x.Text, [ x ]))
                        | _ -> None)

                successes, failures
            else
                // we don't want to do assembly resolution concurrently, we assume MSBuild doesn't handle this
                TcConfig.TryResolveLibsUsingMSBuildRules(tcConfig, assemblyList, rangeStartup, ResolveAssemblyReferenceMode.ReportErrors)

        TcAssemblyResolutions(tcConfig, resolved, unresolved @ knownUnresolved)

    static member GetAllDllReferences(tcConfig: TcConfig) =
        [
            let primaryReference = tcConfig.PrimaryAssemblyDllReference()

            let assumeDotNetFramework = primaryReference.SimpleAssemblyNameIs("mscorlib")

            if not tcConfig.compilingFSharpCore then
                tcConfig.CoreLibraryDllReference()

                if assumeDotNetFramework then
                    // When building desktop then we need these additional dependencies
                    AssemblyReference(rangeStartup, "System.Numerics.dll", None)
                    AssemblyReference(rangeStartup, "System.dll", None)
                    let asm = AssemblyReference(rangeStartup, "netstandard.dll", None)

                    let found =
                        if tcConfig.useSimpleResolution then
                            match tcConfig.ResolveLibWithDirectories(CcuLoadFailureAction.ReturnNone, asm) with
                            | Some _ -> true
                            | None -> false
                        else
                            let references = [| (asm.Text, "") |]

                            let resolutions =
                                tcConfig.MsBuildResolve(references, ResolveAssemblyReferenceMode.Speculative, rangeStartup, false)

                            resolutions.Length = 1

                    if found then
                        asm

            if tcConfig.implicitlyReferenceDotNetAssemblies then
                let references, _useDotNetFramework =
                    tcConfig.FxResolver.GetDefaultReferences(tcConfig.useFsiAuxLib)

                for s in references do
                    let referenceText =
                        if s.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) then
                            s
                        else
                            s + ".dll"

                    AssemblyReference(rangeStartup, referenceText, None)

            yield! tcConfig.referencedDLLs
        ]

    static member SplitNonFoundationalResolutions(tcConfig: TcConfig) =
        let assemblyList = TcAssemblyResolutions.GetAllDllReferences tcConfig

        let resolutions =
            TcAssemblyResolutions.ResolveAssemblyReferences(tcConfig, assemblyList, tcConfig.knownUnresolvedReferences)

        let frameworkDLLs, nonFrameworkReferences =
            resolutions.GetAssemblyResolutions() |> List.partition (fun r -> r.sysdir)

        let unresolved = resolutions.GetUnresolvedReferences()
#if DEBUG
        let mutable itFailed = false

        let addedText =
            "\nIf you want to debug this right now, attach a debugger, and put a breakpoint in 'CompileOps.fs' near the text '!itFailed', and you can re-step through the assembly resolution logic."

        for UnresolvedAssemblyReference(referenceText, _ranges) in unresolved do
            if referenceText.Contains("mscorlib") then
                Debug.Assert(false, sprintf "whoops, did not resolve mscorlib: '%s'%s" referenceText addedText)
                itFailed <- true

        for x in frameworkDLLs do
            if not (FileSystem.IsPathRootedShim(x.resolvedPath)) then
                Debug.Assert(false, sprintf "frameworkDLL should be absolute path: '%s'%s" x.resolvedPath addedText)
                itFailed <- true

        for x in nonFrameworkReferences do
            if not (FileSystem.IsPathRootedShim(x.resolvedPath)) then
                Debug.Assert(false, sprintf "nonFrameworkReference should be absolute path: '%s'%s" x.resolvedPath addedText)
                itFailed <- true

        if itFailed then
            // idea is, put a breakpoint here and then step through
            let assemblyList = TcAssemblyResolutions.GetAllDllReferences tcConfig

            let resolutions =
                TcAssemblyResolutions.ResolveAssemblyReferences(tcConfig, assemblyList, [])

            let _frameworkDLLs, _nonFrameworkReferences =
                resolutions.GetAssemblyResolutions() |> List.partition (fun r -> r.sysdir)

            ()
#endif
        frameworkDLLs, nonFrameworkReferences, unresolved

    static member BuildFromPriorResolutions(tcConfig: TcConfig, resolutions, knownUnresolved) =
        let references = resolutions |> List.map (fun r -> r.originalReference)
        TcAssemblyResolutions.ResolveAssemblyReferences(tcConfig, references, knownUnresolved)

    static member GetAssemblyResolutionInformation(tcConfig: TcConfig) =
        use _ = UseBuildPhase BuildPhase.Parameter
        let assemblyList = TcAssemblyResolutions.GetAllDllReferences tcConfig

        let resolutions =
            TcAssemblyResolutions.ResolveAssemblyReferences(tcConfig, assemblyList, [])

        resolutions.GetAssemblyResolutions(), resolutions.GetUnresolvedReferences()

//----------------------------------------------------------------------------
// Abstraction for project reference

let GetNameOfILModule (m: ILModuleDef) =
    match m.Manifest with
    | Some manifest -> manifest.Name
    | None -> m.Name

let MakeScopeRefForILModule (ilModule: ILModuleDef) =
    match ilModule.Manifest with
    | Some m -> ILScopeRef.Assembly(mkRefToILAssembly m)
    | None -> ILScopeRef.Module(mkRefToILModule ilModule)

let GetCustomAttributesOfILModule (ilModule: ILModuleDef) =
    let attrs =
        match ilModule.Manifest with
        | Some m -> m.CustomAttrs
        | None -> ilModule.CustomAttrs

    attrs.AsList()

let GetAutoOpenAttributes ilModule =
    ilModule |> GetCustomAttributesOfILModule |> List.choose TryFindAutoOpenAttr

let GetInternalsVisibleToAttributes ilModule =
    ilModule
    |> GetCustomAttributesOfILModule
    |> List.choose TryFindInternalsVisibleToAttr

type RawFSharpAssemblyDataBackedByFileOnDisk(ilModule: ILModuleDef, ilAssemblyRefs) =
    let externalSigAndOptData = [ "FSharp.Core" ]

    interface IRawFSharpAssemblyData with

        member _.GetAutoOpenAttributes() = GetAutoOpenAttributes ilModule

        member _.GetInternalsVisibleToAttributes() =
            GetInternalsVisibleToAttributes ilModule

        member _.TryGetILModuleDef() = Some ilModule

        member _.GetRawFSharpSignatureData(m, ilShortAssemName, fileName) =
            let resources = ilModule.Resources.AsList()

            let sigDataReaders = GetResourceNameAndSignatureDataFuncs resources

            let sigDataReaders =
                if sigDataReaders.IsEmpty && List.contains ilShortAssemName externalSigAndOptData then
                    let sigFileName = !!Path.ChangeExtension(fileName, "sigdata")

                    if not (FileSystem.FileExistsShim sigFileName) then
                        error (Error(FSComp.SR.buildExpectedSigdataFile (FileSystem.GetFullPathShim sigFileName), m))

                    let readerA () =
                        FileSystem
                            .OpenFileForReadShim(sigFileName, useMemoryMappedFile = true, shouldShadowCopy = true)
                            .AsByteMemory()
                            .AsReadOnly()

                    [ (ilShortAssemName, (readerA, None)) ]
                else
                    sigDataReaders

            sigDataReaders

        member _.GetRawFSharpOptimizationData(m, ilShortAssemName, fileName) =
            let resources = ilModule.Resources.AsList()

            let optDataReaders = GetResourceNameAndOptimizationDataFuncs resources

            // Look for optimization data in a file
            let optDataReaders =
                if optDataReaders.IsEmpty && List.contains ilShortAssemName externalSigAndOptData then
                    let optDataFile = !!Path.ChangeExtension(fileName, "optdata")

                    if not (FileSystem.FileExistsShim optDataFile) then
                        error (
                            Error(FSComp.SR.buildExpectedFileAlongSideFSharpCore (optDataFile, FileSystem.GetFullPathShim optDataFile), m)
                        )

                    let readerA () =
                        FileSystem
                            .OpenFileForReadShim(optDataFile, useMemoryMappedFile = true, shouldShadowCopy = true)
                            .AsByteMemory()
                            .AsReadOnly()

                    [ (ilShortAssemName, (readerA, None)) ]
                else
                    optDataReaders

            optDataReaders

        member _.GetRawTypeForwarders() =
            match ilModule.Manifest with
            | Some manifest -> manifest.ExportedTypes
            | None -> mkILExportedTypes []

        member _.ShortAssemblyName = GetNameOfILModule ilModule

        member _.ILScopeRef = MakeScopeRefForILModule ilModule

        member _.ILAssemblyRefs = ilAssemblyRefs

        member _.HasAnyFSharpSignatureDataAttribute =
            let attrs = GetCustomAttributesOfILModule ilModule
            List.exists IsSignatureDataVersionAttr attrs

        member _.HasMatchingFSharpSignatureDataAttribute =
            let attrs = GetCustomAttributesOfILModule ilModule
            List.exists (IsMatchingSignatureDataVersionAttr(parseILVersion FSharpBinaryMetadataFormatRevision)) attrs

[<Sealed>]
type RawFSharpAssemblyData(ilModule: ILModuleDef, ilAssemblyRefs) =

    interface IRawFSharpAssemblyData with

        member _.GetAutoOpenAttributes() = GetAutoOpenAttributes ilModule

        member _.GetInternalsVisibleToAttributes() =
            GetInternalsVisibleToAttributes ilModule

        member _.TryGetILModuleDef() = Some ilModule

        member _.GetRawFSharpSignatureData(_, _, _) =
            let resources = ilModule.Resources.AsList()
            GetResourceNameAndSignatureDataFuncs resources

        member _.GetRawFSharpOptimizationData(_, _, _) =
            let resources = ilModule.Resources.AsList()
            GetResourceNameAndOptimizationDataFuncs resources

        member _.GetRawTypeForwarders() =
            match ilModule.Manifest with
            | Some manifest -> manifest.ExportedTypes
            | None -> mkILExportedTypes []

        member _.ShortAssemblyName = GetNameOfILModule ilModule

        member _.ILScopeRef = MakeScopeRefForILModule ilModule

        member _.ILAssemblyRefs = ilAssemblyRefs

        member _.HasAnyFSharpSignatureDataAttribute =
            let attrs = GetCustomAttributesOfILModule ilModule
            List.exists IsSignatureDataVersionAttr attrs

        member _.HasMatchingFSharpSignatureDataAttribute =
            let attrs = GetCustomAttributesOfILModule ilModule
            List.exists (IsMatchingSignatureDataVersionAttr(parseILVersion FSharpBinaryMetadataFormatRevision)) attrs

#endif //!FABLE_COMPILER

//----------------------------------------------------------------------------
// TcImports
//--------------------------------------------------------------------------

#if FABLE_COMPILER

// trimmed-down version of TcImports
[<Sealed>]
type TcImports() =
    let mutable tcGlobalsOpt = None
    let mutable ccuMap = Map<string, ImportedAssembly>([])

    // This is the main "assembly reference --> assembly" resolution routine.
    let FindCcuInfo (_m, assemblyName) =
        match ccuMap |> Map.tryFind assemblyName with
        | Some ccuInfo -> ResolvedCcu(ccuInfo.FSharpViewOfMetadata)
        | None -> UnresolvedCcu(assemblyName)

    member x.FindCcu (_m: range, assemblyName) =
        match ccuMap |> Map.tryFind assemblyName with
        | Some ccuInfo -> Some ccuInfo.FSharpViewOfMetadata
        | None -> None

    member x.SetTcGlobals g =
        tcGlobalsOpt <- Some g
    member x.GetTcGlobals() =
        tcGlobalsOpt.Value
    member x.SetCcuMap m =
        ccuMap <- m
    member x.GetImportedAssemblies() =
        ccuMap.Values |> Seq.toList

    member x.GetImportMap() =
        let loaderInterface =
            { new Import.AssemblyLoader with
                 member _.FindCcuFromAssemblyRef (_ctok, m, ilAssemblyRef) = 
                    FindCcuInfo(m, ilAssemblyRef.Name)
                 member _.TryFindXmlDocumentationInfo (_assemblyName) =
                    None
            }
        new Import.ImportMap (tcGlobalsOpt.Value, loaderInterface)

    member x.GetCcusExcludingBase() =
        //TODO: excludes any framework imports (which may be shared between multiple builds)
        x.GetImportedAssemblies()
        |> List.map (fun x -> x.FSharpViewOfMetadata)

#else //!FABLE_COMPILER

[<Sealed>]
type TcImportsSafeDisposal
    (tciLock: TcImportsLock, disposeActions: ResizeArray<unit -> unit>, disposeTypeProviderActions: ResizeArray<unit -> unit>) =

    let mutable isDisposed = false

    let dispose () =
        tciLock.AcquireLock(fun tcitok ->

            RequireTcImportsLock(tcitok, isDisposed)
            RequireTcImportsLock(tcitok, disposeTypeProviderActions)
            RequireTcImportsLock(tcitok, disposeActions)

            // disposing deliberately only closes this tcImports, not the ones up the chain
            isDisposed <- true

            let actions1 = disposeTypeProviderActions |> Seq.toArray
            let actions2 = disposeActions |> Seq.toArray

            disposeTypeProviderActions.Clear()
            disposeActions.Clear()

            for action in actions1 do
                action ()

            for action in actions2 do
                action ())

    override _.Finalize() = dispose ()

    interface IDisposable with

        member this.Dispose() =
            if not isDisposed then
                GC.SuppressFinalize this
                dispose ()

#if !NO_TYPEPROVIDERS

// TcImports is held as a weak reference inside a TypeProviderConfig.
//
// Due to various historical bugs with the ReferencedAssemblies property of TypeProviderConfig,
// type providers compiled using the TypeProvider SDK have picked up the unfortunate habit of using
// private reflection on the TypeProviderConfig to correctly determine the ReferencedAssemblies.
// These types thus also act as a stable facade supporting exactly the private reflection that is
// used by type providers built with the TypeProvider SDK.
//
// The use of private reflection is highly unfortunate but has historically been the only way to
// unblock several important type providers such as FSharp.Data when the weaknesses in reported
// ReferencedAssemblies were determined.
//
// The use of private reflection was removed from the TypeProvider SDK, see
//     https://github.com/fsprojects/FSharp.TypeProviders.SDK/pull/305. But we still need to work on older type providers.
//
// however it was then reinstated
//
//    https://github.com/fsprojects/FSharp.TypeProviders.SDK/pull/388
//
// All known issues TypeProviderConfig::ReferencedAssemblies are now fixed, meaning one day
// we can remove the use of private reflection from the TPSDK (that is, once the F# tooling fixes
// can be assumed to be shipped in all F# tooling where type providers have to load). After that,
// once all type providers are updated, we will no longer need to have this fixed facade.

/// This acts as a stable type supporting the
type TcImportsDllInfoFacade = { FileName: string }

type TcImportsWeakFacade(tciLock: TcImportsLock, tcImportsWeak: WeakReference<TcImports>) =

    let mutable dllInfos: TcImportsDllInfoFacade list = []

    // The name of these fields must not change, see above
    do assert (nameof dllInfos = "dllInfos")

    member _.SetDllInfos(value: ImportedBinary list) =
        tciLock.AcquireLock(fun tcitok ->
            RequireTcImportsLock(tcitok, dllInfos)

            let infos =
                [
                    for x in value do
                        let info = { FileName = x.FileName }
                        // The name of this field must not change, see above
                        assert (nameof info.FileName = "FileName")
                        info
                ]

            dllInfos <- infos)

    member this.Base: TcImportsWeakFacade option =
        // The name of this property msut not change, see above
        assert (nameof this.Base = "Base")

        match tcImportsWeak.TryGetTarget() with
        | true, tcImports ->
            match tcImports.Base with
            | Some(baseTcImports: TcImports) -> Some baseTcImports.Weak
            | _ -> None
        | _ -> None

    member _.SystemRuntimeContainsType typeName =
        match tcImportsWeak.TryGetTarget() with
        | true, tcImports -> tcImports.SystemRuntimeContainsType typeName
        | _ -> false

    member _.AllAssemblyResolutions() =
        match tcImportsWeak.TryGetTarget() with
        | true, tcImports -> tcImports.AllAssemblyResolutions()
        | _ -> []

#endif
/// Represents a table of imported assemblies with their resolutions.
/// Is a disposable object, but it is recommended not to explicitly call Dispose unless you absolutely know nothing will be using its contents after the disposal.
/// Otherwise, simply allow the GC to collect this and it will properly call Dispose from the finalizer.
and [<Sealed>] TcImports
    (
        tcConfigP: TcConfigProvider,
        initialResolutions: TcAssemblyResolutions,
        importsBase: TcImports option,
        dependencyProviderOpt: DependencyProvider option
    )
#if !NO_TYPEPROVIDERS
    as this
#endif
    =

    let tciLock = TcImportsLock()

    //---- Start protected by tciLock -------
    let mutable resolutions = initialResolutions
    let mutable dllInfos: ImportedBinary list = []
    let mutable dllTable: NameMap<ImportedBinary> = NameMap.empty
    let mutable ccuInfos: ImportedAssembly list = []
    let mutable ccuTable: NameMap<ImportedAssembly> = NameMap.empty
    let mutable ccuThunks = ResizeArray<CcuThunk * (unit -> unit)>()
    let disposeActions = ResizeArray()
    let disposeTypeProviderActions = ResizeArray()

#if !NO_TYPEPROVIDERS
    let mutable generatedTypeRoots =
        Dictionary<ILTypeRef, int * ProviderGeneratedType>()

    let tcImportsWeak = TcImportsWeakFacade(tciLock, WeakReference<_> this)
#endif

    let disposal =
        new TcImportsSafeDisposal(tciLock, disposeActions, disposeTypeProviderActions)
    //---- End protected by tciLock -------

    let mutable disposed = false // this doesn't need locking, it's only for debugging
    let mutable tcGlobals = None // this doesn't need locking, it's set during construction of the TcImports

    let CheckDisposed () =
        if disposed then
            assert false

    let dispose () =
        CheckDisposed()
        (disposal :> IDisposable).Dispose()

    //   Get a snapshot of the current unFixedUp ccuThunks.
    //   for each of those thunks, remove them from the dictionary, so any parallel threads can't do this work
    //      If it successfully removed it from the dictionary then do the fixup
    //          If the thunk remains unresolved add it back to the ccuThunks dictionary for further processing
    //      If not then move on to the next thunk
    let fixupOrphanCcus () =
        tciLock.AcquireLock(fun tcitok ->
            RequireTcImportsLock(tcitok, ccuThunks)
            let contents = ccuThunks |> Seq.toArray

            let unsuccessful =
                [
                    for ccuThunk, func in contents do
                        if ccuThunk.IsUnresolvedReference then
                            func ()

                        if ccuThunk.IsUnresolvedReference then
                            (ccuThunk, func)
                ]

            ccuThunks <- ResizeArray unsuccessful)

    let availableToOptionalCcu =
        function
        | ResolvedCcu ccu -> Some ccu
        | UnresolvedCcu _ -> None

    static let ccuHasType (ccu: CcuThunk) (nsname: string list) (tname: string) (publicOnly: bool) =
        let matchNameSpace (entityOpt: Entity option) n =
            match entityOpt with
            | None -> None
            | Some entity -> entity.ModuleOrNamespaceType.AllEntitiesByCompiledAndLogicalMangledNames.TryFind n

        match (Some ccu.Contents, nsname) ||> List.fold matchNameSpace with
        | Some ns ->
            match Map.tryFind tname ns.ModuleOrNamespaceType.TypesByMangledName with
            | Some e ->
                if publicOnly then
                    match e.TypeReprInfo with
                    | TILObjectRepr data ->
                        let (TILObjectReprData(_, _, tyDef)) = data
                        tyDef.Access = ILTypeDefAccess.Public
                    | _ -> false
                else
                    true
            | None -> false
        | None -> false

    member internal _.Base =
        CheckDisposed()
        importsBase

    member _.CcuTable =
        tciLock.AcquireLock(fun tcitok ->
            RequireTcImportsLock(tcitok, ccuTable)
            CheckDisposed()
            ccuTable)

    member _.DllTable =
        tciLock.AcquireLock(fun tcitok ->
            RequireTcImportsLock(tcitok, dllTable)
            CheckDisposed()
            dllTable)

#if !NO_TYPEPROVIDERS
    member _.Weak =
        CheckDisposed()
        tcImportsWeak
#endif

    member _.RegisterCcu ccuInfo =
        tciLock.AcquireLock(fun tcitok ->
            CheckDisposed()
            RequireTcImportsLock(tcitok, ccuInfos)
            RequireTcImportsLock(tcitok, ccuTable)
            ccuInfos <- ccuInfos ++ ccuInfo
            // Assembly Ref Resolution: remove this use of ccu.AssemblyName
            ccuTable <- NameMap.add ccuInfo.FSharpViewOfMetadata.AssemblyName ccuInfo ccuTable)

    member _.RegisterDll dllInfo =
        tciLock.AcquireLock(fun tcitok ->
            CheckDisposed()
            RequireTcImportsLock(tcitok, dllInfos)
            RequireTcImportsLock(tcitok, dllTable)
            dllInfos <- dllInfos ++ dllInfo
#if !NO_TYPEPROVIDERS
            tcImportsWeak.SetDllInfos dllInfos
#endif
            dllTable <- NameMap.add (getNameOfScopeRef dllInfo.ILScopeRef) dllInfo dllTable)

    member _.GetDllInfos() : ImportedBinary list =
        tciLock.AcquireLock(fun tcitok ->
            CheckDisposed()
            RequireTcImportsLock(tcitok, dllInfos)

            match importsBase with
            | Some importsBase -> importsBase.GetDllInfos() @ dllInfos
            | None -> dllInfos)

    member _.AllAssemblyResolutions() =
        tciLock.AcquireLock(fun tcitok ->
            CheckDisposed()
            RequireTcImportsLock(tcitok, resolutions)
            let ars = resolutions.GetAssemblyResolutions()

            match importsBase with
            | Some importsBase -> importsBase.AllAssemblyResolutions() @ ars
            | None -> ars)

    member tcImports.TryFindDllInfo(ctok: CompilationThreadToken, m, assemblyName, lookupOnly) =
        CheckDisposed()

        let rec look (t: TcImports) =
            match NameMap.tryFind assemblyName t.DllTable with
            | Some res -> Some res
            | None ->
                match t.Base with
                | Some t2 -> look t2
                | None -> None

        match look tcImports with
        | Some res -> Some res
        | None ->
            tcImports.ImplicitLoadIfAllowed(ctok, m, assemblyName, lookupOnly)
            look tcImports

    member tcImports.FindDllInfo(ctok, m, assemblyName) =
        match tcImports.TryFindDllInfo(ctok, m, assemblyName, lookupOnly = false) with
        | Some res -> res
        | None -> error (Error(FSComp.SR.buildCouldNotResolveAssembly assemblyName, m))

    member _.GetImportedAssemblies() =
        tciLock.AcquireLock(fun tcitok ->
            CheckDisposed()
            RequireTcImportsLock(tcitok, ccuInfos)

            match importsBase with
            | Some importsBase -> List.append (importsBase.GetImportedAssemblies()) ccuInfos
            | None -> ccuInfos)

    member _.GetCcusExcludingBase() =
        tciLock.AcquireLock(fun tcitok ->
            CheckDisposed()
            RequireTcImportsLock(tcitok, ccuInfos)
            ccuInfos |> List.map (fun x -> x.FSharpViewOfMetadata))

    member tcImports.GetCcusInDeclOrder() =
        CheckDisposed()
        List.map (fun x -> x.FSharpViewOfMetadata) (tcImports.GetImportedAssemblies())

    // This is the main "assembly reference --> assembly" resolution routine.
    member tcImports.FindCcuInfo(ctok, m, assemblyName, lookupOnly) =
        CheckDisposed()

        let rec look (t: TcImports) =
            match NameMap.tryFind assemblyName t.CcuTable with
            | Some res -> Some res
            | None ->
                match t.Base with
                | Some t2 -> look t2
                | None -> None

        match look tcImports with
        | Some res -> ResolvedImportedAssembly(res, m)
        | None ->
            tcImports.ImplicitLoadIfAllowed(ctok, m, assemblyName, lookupOnly)

            match look tcImports with
            | Some res -> ResolvedImportedAssembly(res, m)
            | None -> UnresolvedImportedAssembly(assemblyName, m)

    member tcImports.FindCcu(ctok, m, assemblyName, lookupOnly) =
        CheckDisposed()

        match tcImports.FindCcuInfo(ctok, m, assemblyName, lookupOnly) with
        | ResolvedImportedAssembly(importedAssembly, _) -> ResolvedCcu(importedAssembly.FSharpViewOfMetadata)
        | UnresolvedImportedAssembly(assemblyName, _) -> UnresolvedCcu assemblyName

    member tcImports.FindCcuFromAssemblyRef(ctok, m, assemblyRef: ILAssemblyRef) =
        CheckDisposed()

        match tcImports.FindCcuInfo(ctok, m, assemblyRef.Name, lookupOnly = false) with
        | ResolvedImportedAssembly(importedAssembly, _) -> ResolvedCcu(importedAssembly.FSharpViewOfMetadata)
        | UnresolvedImportedAssembly _ -> UnresolvedCcu(assemblyRef.QualifiedName)

    member tcImports.TryFindXmlDocumentationInfo(assemblyName: string) =
        CheckDisposed()

        let rec look (t: TcImports) =
            match NameMap.tryFind assemblyName t.CcuTable with
            | Some res -> Some res
            | None ->
                match t.Base with
                | Some t2 -> look t2
                | None -> None

        match look tcImports with
        | Some res -> res.FSharpViewOfMetadata.Deref.XmlDocumentationInfo
        | _ -> None

#if !NO_TYPEPROVIDERS
    member tcImports.GetProvidedAssemblyInfo(ctok, m, assembly: Tainted<ProvidedAssembly MaybeNull>) =
        match assembly with
        | Tainted.Null -> false, None
        | Tainted.NonNull assembly ->
            let aname = assembly.PUntaint((fun a -> a.GetName()), m)
            let ilShortAssemName = string aname.Name

            match tcImports.FindCcu(ctok, m, ilShortAssemName, lookupOnly = true) with
            | ResolvedCcu ccu ->
                if ccu.IsProviderGenerated then
                    let dllinfo = tcImports.FindDllInfo(ctok, m, ilShortAssemName)
                    true, dllinfo.ProviderGeneratedStaticLinkMap
                else
                    false, None

            | UnresolvedCcu _ ->
                let g = tcImports.GetTcGlobals()
                let ilScopeRef = ILScopeRef.Assembly(ILAssemblyRef.FromAssemblyName aname)
                let fileName = string aname.Name + ".dll"

                let bytes =
                    assembly
                        .PApplyWithProvider((fun (assembly, provider) -> assembly.GetManifestModuleContents provider), m)
                        .PUntaint(id, m)

                let tcConfig = tcConfigP.Get ctok

                let ilModule, ilAssemblyRefs =
                    let opts: ILReaderOptions =
                        {
                            reduceMemoryUsage = tcConfig.reduceMemoryUsage
                            pdbDirPath = None
                            metadataOnly = MetadataOnlyFlag.Yes
                            tryGetMetadataSnapshot = tcConfig.tryGetMetadataSnapshot
                        }

                    let reader = OpenILModuleReaderFromBytes fileName bytes opts
                    reader.ILModuleDef, reader.ILAssemblyRefs

                let theActualAssembly = assembly.PUntaint((fun x -> x.Handle), m)

                let dllinfo =
                    {
                        RawMetadata = RawFSharpAssemblyDataBackedByFileOnDisk(ilModule, ilAssemblyRefs)
                        FileName = fileName
                        ProviderGeneratedAssembly = Some theActualAssembly
                        IsProviderGenerated = true
                        ProviderGeneratedStaticLinkMap =
                            if g.isInteractive then
                                None
                            else
                                Some(ProvidedAssemblyStaticLinkingMap.CreateNew())
                        ILScopeRef = ilScopeRef
                        ILAssemblyRefs = ilAssemblyRefs
                    }

                tcImports.RegisterDll dllinfo

                let ccuContents =
                    Construct.NewCcuContents ilScopeRef m ilShortAssemName (Construct.NewEmptyModuleOrNamespaceType(Namespace true))

                let ccuData: CcuData =
                    {
                        IsFSharp = false
                        UsesFSharp20PlusQuotations = false
                        InvalidateEvent = Event<_>().Publish
                        IsProviderGenerated = true
                        QualifiedName = Some(assembly.PUntaint((fun a -> a.FullName), m))
                        Contents = ccuContents
                        ILScopeRef = ilScopeRef
                        Stamp = newStamp ()
                        SourceCodeDirectory = ""
                        FileName = Some fileName
                        MemberSignatureEquality = (fun ty1 ty2 -> typeEquivAux EraseAll g ty1 ty2)
                        ImportProvidedType = (fun ty -> ImportProvidedType (tcImports.GetImportMap()) m ty)
                        TryGetILModuleDef = (fun () -> Some ilModule)
                        TypeForwarders = CcuTypeForwarderTable.Empty
                        XmlDocumentationInfo =
                            match tcConfig.xmlDocInfoLoader with
                            | Some xmlDocInfoLoader -> xmlDocInfoLoader.TryLoad(fileName)
                            | _ -> None
                    }

                let ccu = CcuThunk.Create(ilShortAssemName, ccuData)

                let ccuinfo =
                    {
                        FSharpViewOfMetadata = ccu
                        ILScopeRef = ilScopeRef
                        AssemblyAutoOpenAttributes = []
                        AssemblyInternalsVisibleToAttributes = []
                        IsProviderGenerated = true
                        TypeProviders = []
                        FSharpOptimizationData = notlazy None
                    }

                tcImports.RegisterCcu ccuinfo
                // Yes, it is generative
                true, dllinfo.ProviderGeneratedStaticLinkMap

    member _.RecordGeneratedTypeRoot root =
        tciLock.AcquireLock(fun tcitok ->
            // checking if given ProviderGeneratedType was already recorded before (probably for another set of static parameters)
            let (ProviderGeneratedType(_, ilTyRef, _)) = root

            let index =
                RequireTcImportsLock(tcitok, generatedTypeRoots)

                match generatedTypeRoots.TryGetValue ilTyRef with
                | true, (index, _) -> index
                | false, _ -> generatedTypeRoots.Count

            generatedTypeRoots[ilTyRef] <- (index, root))

    member _.ProviderGeneratedTypeRoots =
        tciLock.AcquireLock(fun tcitok ->
            RequireTcImportsLock(tcitok, generatedTypeRoots)
            generatedTypeRoots.Values |> Seq.sortBy fst |> Seq.map snd |> Seq.toList)
#endif

    member private tcImports.AttachDisposeAction action =
        tciLock.AcquireLock(fun tcitok ->
            CheckDisposed()
            RequireTcImportsLock(tcitok, disposeActions)
            disposeActions.Add action)

#if !NO_TYPEPROVIDERS
    member private tcImports.AttachDisposeTypeProviderAction action =
        tciLock.AcquireLock(fun tcitok ->
            CheckDisposed()
            RequireTcImportsLock(tcitok, disposeTypeProviderActions)
            disposeTypeProviderActions.Add action)
#endif

    // Note: the returned binary reader is associated with the tcImports, i.e. when the tcImports are closed
    // then the reader is closed.
    member tcImports.OpenILBinaryModule(ctok, fileName, m) =
        try
            CheckDisposed()
            let tcConfig = tcConfigP.Get ctok

            let pdbDirPath =
                // We open the pdb file if one exists parallel to the binary we
                // are reading, so that --standalone will preserve debug information.
                if tcConfig.openDebugInformationForLaterStaticLinking then
                    let pdbDir =
                        try
                            FileSystem.GetDirectoryNameShim fileName
                        with _ ->
                            "."

                    let pdbFile =
                        (try
                            FileSystemUtils.chopExtension fileName
                         with _ ->
                             fileName)
                        + ".pdb"

                    if FileSystem.FileExistsShim pdbFile then
                        Some pdbDir
                    else
                        None
                else
                    None

            let ilILBinaryReader =
                OpenILBinary(
                    fileName,
                    tcConfig.reduceMemoryUsage,
                    pdbDirPath,
                    tcConfig.shadowCopyReferences,
                    tcConfig.tryGetMetadataSnapshot
                )

            tcImports.AttachDisposeAction(fun _ -> (ilILBinaryReader :> IDisposable).Dispose())
            ilILBinaryReader.ILModuleDef, ilILBinaryReader.ILAssemblyRefs
        with e ->
            error (Error(FSComp.SR.buildErrorOpeningBinaryFile (fileName, e.Message), m))

    (* auxModTable is used for multi-module assemblies *)
    member tcImports.MkLoaderForMultiModuleILAssemblies ctok m =
        CheckDisposed()
        let auxModTable = HashMultiMap(10, HashIdentity.Structural)

        fun viewedScopeRef ->

            let tcConfig = tcConfigP.Get ctok

            match viewedScopeRef with
            | ILScopeRef.Module modref ->
                let key = modref.Name

                if not (auxModTable.ContainsKey key) then
                    let resolution =
                        tcConfig.ResolveLibWithDirectories(CcuLoadFailureAction.RaiseError, AssemblyReference(m, key, None))
                        |> Option.get

                    let ilModule, _ = tcImports.OpenILBinaryModule(ctok, resolution.resolvedPath, m)
                    auxModTable[key] <- ilModule

                auxModTable[key]

            | _ -> error (InternalError("Unexpected ILScopeRef.Local or ILScopeRef.Assembly in exported type table", m))

    member tcImports.IsAlreadyRegistered nm =
        CheckDisposed()

        tcImports.GetDllInfos()
        |> List.exists (fun dll ->
            match dll.ILScopeRef with
            | ILScopeRef.Assembly a -> a.Name = nm
            | _ -> false)

    member _.DependencyProvider =
        CheckDisposed()

        match dependencyProviderOpt with
        | None ->
            Debug.Assert(false, "this should never be called on FrameworkTcImports")
            new DependencyProvider()
        | Some dependencyProvider -> dependencyProvider

    member tcImports.GetImportMap() =
        CheckDisposed()

        let loaderInterface =
#if NO_TYPEPROVIDERS
            { new AssemblyLoader with
                member _.FindCcuFromAssemblyRef(ctok, m, ilAssemblyRef) =
                    tcImports.FindCcuFromAssemblyRef(ctok, m, ilAssemblyRef)

                member _.TryFindXmlDocumentationInfo assemblyName =
                    tcImports.TryFindXmlDocumentationInfo(assemblyName)
            }
#else
            { new AssemblyLoader with
                member _.FindCcuFromAssemblyRef(ctok, m, ilAssemblyRef) =
                    tcImports.FindCcuFromAssemblyRef(ctok, m, ilAssemblyRef)

                member _.TryFindXmlDocumentationInfo assemblyName =
                    tcImports.TryFindXmlDocumentationInfo(assemblyName)

                member _.GetProvidedAssemblyInfo(ctok, m, assembly) =
                    tcImports.GetProvidedAssemblyInfo(ctok, m, assembly)

                member _.RecordGeneratedTypeRoot root = tcImports.RecordGeneratedTypeRoot root
            }
#endif
        ImportMap(tcImports.GetTcGlobals(), loaderInterface)

    // Note the tcGlobals are only available once mscorlib and fslib have been established. For TcImports,
    // they are logically only needed when converting AbsIL data structures into F# data structures, and
    // when converting AbsIL types in particular, since these types are normalized through the tables
    // in the tcGlobals (E.g. normalizing 'System.Int32' to 'int'). On the whole ImportILAssembly doesn't
    // actually convert AbsIL types - it only converts the outer shell of type definitions - the vast majority of
    // types such as those in method signatures are currently converted on-demand. However ImportILAssembly does have to
    // convert the types that are constraints in generic parameters, which was the original motivation for making sure that
    // ImportILAssembly had a tcGlobals available when it really needs it.
    member _.GetTcGlobals() : TcGlobals =
        CheckDisposed()

        match tcGlobals with
        | Some g -> g
        | None ->
            match importsBase with
            | Some b -> b.GetTcGlobals()
            | None -> failwith "unreachable: GetGlobals - are the references to mscorlib.dll and FSharp.Core.dll valid?"

    member private tcImports.SetTcGlobals g =
        CheckDisposed()
        tcGlobals <- Some g

#if !NO_TYPEPROVIDERS
    member private tcImports.InjectProvidedNamespaceOrTypeIntoEntity
        (
            typeProviderEnvironment,
            tcConfig: TcConfig,
            m,
            entity: Entity,
            injectedNamespace,
            remainingNamespace,
            provider,
            st: Tainted<ProvidedType> option
        ) =
        match remainingNamespace with
        | next :: rest ->
            // Inject the namespace entity
            match entity.ModuleOrNamespaceType.ModulesAndNamespacesByDemangledName.TryFind next with
            | Some childEntity ->
                tcImports.InjectProvidedNamespaceOrTypeIntoEntity(
                    typeProviderEnvironment,
                    tcConfig,
                    m,
                    childEntity,
                    next :: injectedNamespace,
                    rest,
                    provider,
                    st
                )
            | None ->
                // Build up the artificial namespace if there is not a real one.
                let cpath =
                    CompPath(
                        ILScopeRef.Local,
                        SyntaxAccess.Unknown,
                        injectedNamespace
                        |> List.rev
                        |> List.map (fun n -> (n, ModuleOrNamespaceKind.Namespace true))
                    )

                let mid = ident (next, rangeStartup)
                let mty = Construct.NewEmptyModuleOrNamespaceType(Namespace true)

                let newNamespace =
                    Construct.NewModuleOrNamespace (Some cpath) taccessPublic mid XmlDoc.Empty [] (MaybeLazy.Strict mty)

                entity.ModuleOrNamespaceType.AddModuleOrNamespaceByMutation newNamespace

                tcImports.InjectProvidedNamespaceOrTypeIntoEntity(
                    typeProviderEnvironment,
                    tcConfig,
                    m,
                    newNamespace,
                    next :: injectedNamespace,
                    rest,
                    provider,
                    st
                )
        | [] ->
            match st with
            | Some st ->
                // Inject the wrapper type into the provider assembly.
                //
                // Generated types get properly injected into the provided (i.e. generated) assembly CCU in tc.fs

                let importProvidedType t =
                    ImportProvidedType (tcImports.GetImportMap()) m t

                let isSuppressRelocate =
                    tcConfig.isInteractive || st.PUntaint((fun st -> st.IsSuppressRelocate), m)

                let newEntity =
                    Construct.NewProvidedTycon(typeProviderEnvironment, st, importProvidedType, isSuppressRelocate, m)

                entity.ModuleOrNamespaceType.AddProvidedTypeEntity newEntity
            | None -> ()

            entity.entity_tycon_repr <-
                match entity.TypeReprInfo with
                // This is the first extension
                | TNoRepr -> TProvidedNamespaceRepr(typeProviderEnvironment, [ provider ])

                // Add to the existing list of extensions
                | TProvidedNamespaceRepr(resolutionFolder, prior) as repr ->
                    if not (prior |> List.exists (fun r -> Tainted.EqTainted r provider)) then
                        TProvidedNamespaceRepr(resolutionFolder, provider :: prior)
                    else
                        repr

                | _ -> failwith "Unexpected representation in namespace entity referred to by a type provider"

    member tcImportsStrong.ImportTypeProviderExtensions
        (
            ctok,
            tcConfig: TcConfig,
            fileNameOfRuntimeAssembly,
            ilScopeRefOfRuntimeAssembly,
            runtimeAssemblyAttributes: ILAttribute list,
            entityToInjectInto,
            invalidateCcu: Event<_>,
            m
        ) =

        let startingErrorCount = DiagnosticsThreadStatics.DiagnosticsLogger.ErrorCount

        // Find assembly level TypeProviderAssemblyAttributes. These will point to the assemblies that
        // have class which implement ITypeProvider and which have TypeProviderAttribute on them.
        let designTimeAssemblyNames =
            runtimeAssemblyAttributes
            |> List.choose TryDecodeTypeProviderAssemblyAttr
            // If no design-time assembly is specified, use the runtime assembly
            |> List.map (function
                | Null -> fileNameOfRuntimeAssembly
                | NonNull s -> s)
            // For each simple name of a design-time assembly, we take the first matching one in the order they are
            // specified in the attributes
            |> List.distinctBy (fun s ->
                try
                    Path.GetFileNameWithoutExtension s
                with _ ->
                    s)

        if not (List.isEmpty designTimeAssemblyNames) then

            // Find the SystemRuntimeAssemblyVersion value to report in the TypeProviderConfig.
            let primaryAssemblyVersion =
                let primaryAssemblyRef = tcConfig.PrimaryAssemblyDllReference()

                let resolution =
                    tcConfig.ResolveLibWithDirectories(CcuLoadFailureAction.RaiseError, primaryAssemblyRef)
                    |> Option.get
                // MSDN: this method causes the file to be opened and closed, but the assembly is not added to this domain
                let name = AssemblyName.GetAssemblyName(resolution.resolvedPath)
                !!name.Version

            // Note, this only captures systemRuntimeContainsTypeRef (which captures tcImportsWeak, using name tcImports)
            let systemRuntimeContainsType =
                let tcImports = tcImportsWeak

                // The name of this captured value must not change, see comments on TcImportsWeakFacade above
                assert (nameof tcImports = "tcImports")

                let mutable systemRuntimeContainsTypeRef = tcImports.SystemRuntimeContainsType

                // When the tcImports is disposed the systemRuntimeContainsTypeRef thunk is replaced
                // with one raising an exception.
                tcImportsStrong.AttachDisposeTypeProviderAction(fun () ->
                    systemRuntimeContainsTypeRef <- fun _ -> raise (ObjectDisposedException("The type provider has been disposed")))

                systemRuntimeContainsTypeRef

            // Note, this only captures tcImportsWeak
            let mutable getReferencedAssemblies =
                (fun () -> [| for r in tcImportsWeak.AllAssemblyResolutions() -> r.resolvedPath |])

            // When the tcImports is disposed the getReferencedAssemblies thunk is replaced
            // with one raising an exception.
            tcImportsStrong.AttachDisposeTypeProviderAction(fun () ->
                getReferencedAssemblies <- fun _ -> raise (ObjectDisposedException("The type provider has been disposed")))

            let typeProviderEnvironment =
                {
                    ResolutionFolder = tcConfig.implicitIncludeDir
                    OutputFile = tcConfig.outputFile
                    ShowResolutionMessages = tcConfig.showExtensionTypeMessages
                    GetReferencedAssemblies = (fun () -> [| for r in tcImportsStrong.AllAssemblyResolutions() -> r.resolvedPath |])
                    TemporaryFolder = FileSystem.GetTempPathShim()
                }

            let providers =
                [
                    for designTimeAssemblyName in designTimeAssemblyNames do
                        yield!
                            GetTypeProvidersOfAssembly(
                                fileNameOfRuntimeAssembly,
                                ilScopeRefOfRuntimeAssembly,
                                designTimeAssemblyName,
                                typeProviderEnvironment,
                                tcConfig.isInvalidationSupported,
                                tcConfig.isInteractive,
                                systemRuntimeContainsType,
                                primaryAssemblyVersion,
                                tcConfig.compilerToolPaths,
                                m
                            )
                ]
            // Note, type providers are disposable objects. The TcImports owns the provider objects - when/if it is disposed, the providers are disposed.
            // We ignore all exceptions from provider disposal.
            for provider in providers do
                tcImportsStrong.AttachDisposeTypeProviderAction(fun () ->
                    try
                        provider.PUntaintNoFailure(fun x -> x.Dispose())
                    with e ->
                        ())

            // Add the invalidation signal handlers to each provider
            for provider in providers do
                provider.PUntaint(
                    (fun tp ->

                        // Register the type provider invalidation handler.
                        //
                        // We are explicit about what the handler closure captures to help reason about the
                        // lifetime of captured objects, especially in case the type provider instance gets leaked
                        // or keeps itself alive mistakenly, e.g. via some global state in the type provider instance.
                        //
                        // The closure captures
                        //   1. an Event value, ultimately this is made available in all CCus as ccu.InvalidateEvent
                        //   2. any handlers registered to ccu.InvalidateEvent
                        //   3. a message string
                        //
                        // Note that the invalidation handler does not explicitly capture the TcImports.
                        // The only place where handlers are registered is to ccu.InvalidateEvent is in IncrementalBuilder.fs.

                        let capturedInvalidateCcu = invalidateCcu

                        let capturedMessage =
                            "The provider '" + fileNameOfRuntimeAssembly + "' reported a change"

                        let handler =
                            tp.Invalidate.Subscribe(fun _ -> capturedInvalidateCcu.Trigger capturedMessage)

                        // When the TcImports is disposed we detach the invalidation callback
                        tcImportsStrong.AttachDisposeTypeProviderAction(fun () ->
                            try
                                handler.Dispose()
                            with _ ->
                                ())),
                    m
                )

            match providers with
            | [] ->
                let typeName = !!typeof<TypeProviderAssemblyAttribute>.FullName
                warning (Error(FSComp.SR.etHostingAssemblyFoundWithoutHosts (fileNameOfRuntimeAssembly, typeName), m))
            | _ ->

#if DEBUG
                if typeProviderEnvironment.ShowResolutionMessages then
                    dprintfn "Found extension type hosting assembly '%s' with the following extensions:" fileNameOfRuntimeAssembly

                    providers
                    |> List.iter (fun provider -> dprintfn " %s" (DisplayNameOfTypeProvider(provider.TypeProvider, m)))
#endif

                for provider in providers do
                    try
                        // Inject an entity for the namespace, or if one already exists, then record this as a provider
                        // for that namespace.
                        let rec loop (providedNamespace: Tainted<IProvidedNamespace>) =
                            let path =
                                GetProvidedNamespaceAsPath(m, provider, providedNamespace.PUntaint((fun r -> r.NamespaceName), m))

                            tcImportsStrong.InjectProvidedNamespaceOrTypeIntoEntity(
                                typeProviderEnvironment,
                                tcConfig,
                                m,
                                entityToInjectInto,
                                [],
                                path,
                                provider,
                                None
                            )

                            // Inject entities for the types returned by provider.GetTypes().
                            //
                            // NOTE: The types provided by GetTypes() are available for name resolution
                            // when the namespace is "opened". This is part of the specification of the language
                            // feature.
                            let tys =
                                providedNamespace.PApplyArray((fun provider -> provider.GetTypes()), "GetTypes", m)

                            let ptys =
                                [|
                                    for ty in tys -> ty.PApply((fun ty -> ty |> ProvidedType.CreateNoContext), m)
                                |]

                            for st in ptys do
                                tcImportsStrong.InjectProvidedNamespaceOrTypeIntoEntity(
                                    typeProviderEnvironment,
                                    tcConfig,
                                    m,
                                    entityToInjectInto,
                                    [],
                                    path,
                                    provider,
                                    Some st
                                )

                            for providedNestedNamespace in
                                providedNamespace.PApplyArray((fun provider -> provider.GetNestedNamespaces()), "GetNestedNamespaces", m) do
                                loop providedNestedNamespace

                        RequireCompilationThread ctok // IProvidedType.GetNamespaces is an example of a type provider call

                        let providedNamespaces =
                            provider.PApplyArray((fun r -> r.GetNamespaces()), "GetNamespaces", m)

                        for providedNamespace in providedNamespaces do
                            loop providedNamespace
                    with RecoverableException e ->
                        errorRecovery e m

                if startingErrorCount < DiagnosticsThreadStatics.DiagnosticsLogger.ErrorCount then
                    error (Error(FSComp.SR.etOneOrMoreErrorsSeenDuringExtensionTypeSetting (), m))

            providers
        else
            []
#endif

    /// Query information about types available in target system runtime library
    member tcImports.SystemRuntimeContainsType(typeName: string) : bool =
        let ns, typeName = splitILTypeName typeName
        let tcGlobals = tcImports.GetTcGlobals()
        tcGlobals.TryFindSysTyconRef ns typeName |> Option.isSome

    // Add a referenced assembly
    //
    // Retargetable assembly refs are required for binaries that must run
    // against DLLs supported by multiple publishers. For example
    // Compact Framework binaries must use this. However it is not
    // clear when else it is required, e.g. for Mono.

    member tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo: ImportedBinary) =
        CheckDisposed()
        let tcConfig = tcConfigP.Get ctok
        assert dllinfo.RawMetadata.TryGetILModuleDef().IsSome
        let ilModule = dllinfo.RawMetadata.TryGetILModuleDef().Value
        let ilScopeRef = dllinfo.ILScopeRef
        let auxModuleLoader = tcImports.MkLoaderForMultiModuleILAssemblies ctok m
        let invalidateCcu = Event<_>()

        let ccu =
            ImportILAssembly(
                tcImports.GetImportMap,
                m,
                auxModuleLoader,
                tcConfig.xmlDocInfoLoader,
                ilScopeRef,
                tcConfig.implicitIncludeDir,
                Some fileName,
                ilModule,
                invalidateCcu.Publish
            )

        let ccuinfo =
            {
                FSharpViewOfMetadata = ccu
                ILScopeRef = ilScopeRef
                AssemblyAutoOpenAttributes = GetAutoOpenAttributes ilModule
                AssemblyInternalsVisibleToAttributes = GetInternalsVisibleToAttributes ilModule
#if !NO_TYPEPROVIDERS
                IsProviderGenerated = false
                TypeProviders = []
#endif
                FSharpOptimizationData = notlazy None
            }

        tcImports.RegisterCcu ccuinfo

        let phase2 () =
#if !NO_TYPEPROVIDERS
            let attrs = ilModule.ManifestOfAssembly.CustomAttrs.AsList()

            ccuinfo.TypeProviders <-
                tcImports.ImportTypeProviderExtensions(ctok, tcConfig, fileName, ilScopeRef, attrs, ccu.Contents, invalidateCcu, m)
#endif
            [ ResolvedImportedAssembly(ccuinfo, m) ]

        phase2

    member tcImports.PrepareToImportReferencedFSharpAssembly(ctok, m, fileName, dllinfo: ImportedBinary) =
        CheckDisposed()
#if !NO_TYPEPROVIDERS
        let tcConfig = tcConfigP.Get ctok
#endif
        let ilModule = dllinfo.RawMetadata
        let ilScopeRef = dllinfo.ILScopeRef
        let ilShortAssemName = getNameOfScopeRef ilScopeRef

        let optDataReaders =
            ilModule.GetRawFSharpOptimizationData(m, ilShortAssemName, fileName)

        let ccuRawDataAndInfos =
            ilModule.GetRawFSharpSignatureData(m, ilShortAssemName, fileName)
            |> List.map (fun (ccuName, (sigDataReader, sigDataReaderB)) ->
                let data =
                    GetSignatureData(fileName, ilScopeRef, ilModule.TryGetILModuleDef(), sigDataReader, sigDataReaderB)

                let optDatas = Map.ofList optDataReaders

                let minfo: PickledCcuInfo = data.RawData
                let mspec = minfo.mspec

                if mspec.DisplayName = "FSharp.Core" then
                    updateSeqTypeIsPrefix mspec

#if !NO_TYPEPROVIDERS
                let invalidateCcu = Event<_>()
#endif

                let codeDir = minfo.compileTimeWorkingDir

                // note: for some fields we fix up this information later
                let ccuData: CcuData =
                    {
                        ILScopeRef = ilScopeRef
                        Stamp = newStamp ()
                        FileName = Some fileName
                        QualifiedName = Some(ilScopeRef.QualifiedName)
                        SourceCodeDirectory = codeDir
                        IsFSharp = true
                        Contents = mspec
#if !NO_TYPEPROVIDERS
                        InvalidateEvent = invalidateCcu.Publish
                        IsProviderGenerated = false
                        ImportProvidedType = (fun ty -> ImportProvidedType (tcImports.GetImportMap()) m ty)
#endif
                        TryGetILModuleDef = ilModule.TryGetILModuleDef
                        UsesFSharp20PlusQuotations = minfo.usesQuotations
                        MemberSignatureEquality = (fun ty1 ty2 -> typeEquivAux EraseAll (tcImports.GetTcGlobals()) ty1 ty2)
                        TypeForwarders = ImportILAssemblyTypeForwarders(tcImports.GetImportMap, m, ilModule.GetRawTypeForwarders())
#if !NO_TYPEPROVIDERS
                        XmlDocumentationInfo =
                            match tcConfig.xmlDocInfoLoader with
                            | Some xmlDocInfoLoader -> xmlDocInfoLoader.TryLoad(fileName)
                            | _ -> None
#else
                        XmlDocumentationInfo = None
#endif
                    }

                let ccu = CcuThunk.Create(ccuName, ccuData)

                let optdata =
                    InterruptibleLazy(fun _ ->
                        match Map.tryFind ccuName optDatas with
                        | None -> None
                        | Some(readerA, readerB) ->
                            let data =
                                GetOptimizationData(fileName, ilScopeRef, ilModule.TryGetILModuleDef(), readerA, readerB)

                            let fixupThunk () =
                                data.OptionalFixup(fun nm -> availableToOptionalCcu (tcImports.FindCcu(ctok, m, nm, lookupOnly = false)))

                            // Make a note of all ccuThunks that may still need to be fixed up when other dlls are loaded
                            tciLock.AcquireLock(fun tcitok ->
                                RequireTcImportsLock(tcitok, ccuThunks)

                                for ccuThunk in data.FixupThunks do
                                    if ccuThunk.IsUnresolvedReference then
                                        ccuThunks.Add(ccuThunk, (fun () -> fixupThunk () |> ignore)))

                            Some(fixupThunk ()))

                let ccuinfo =
                    {
                        FSharpViewOfMetadata = ccu
                        AssemblyAutoOpenAttributes = ilModule.GetAutoOpenAttributes()
                        AssemblyInternalsVisibleToAttributes = ilModule.GetInternalsVisibleToAttributes()
                        FSharpOptimizationData = optdata
#if !NO_TYPEPROVIDERS
                        IsProviderGenerated = false
                        TypeProviders = []
#endif
                        ILScopeRef = ilScopeRef
                    }

                let phase2 () =
#if !NO_TYPEPROVIDERS
                    match ilModule.TryGetILModuleDef() with
                    | None -> () // no type providers can be used without a real IL Module present
                    | Some ilModule ->
                        let attrs = ilModule.ManifestOfAssembly.CustomAttrs.AsList()

                        let tps =
                            tcImports.ImportTypeProviderExtensions(
                                ctok,
                                tcConfig,
                                fileName,
                                ilScopeRef,
                                attrs,
                                ccu.Contents,
                                invalidateCcu,
                                m
                            )

                        ccuinfo.TypeProviders <- tps
#else
                    ()
#endif
                data, ccuinfo, phase2)

        // Register all before relinking to cope with mutually-referential ccus
        ccuRawDataAndInfos |> List.iter (p23 >> tcImports.RegisterCcu)

        let phase2 () =
            // Relink
            ccuRawDataAndInfos
            |> List.iter (fun (data, _, _) ->
                let fixupThunk () =
                    data.OptionalFixup(fun nm -> availableToOptionalCcu (tcImports.FindCcu(ctok, m, nm, lookupOnly = false)))
                    |> ignore

                fixupThunk ()

                for ccuThunk in data.FixupThunks do
                    if ccuThunk.IsUnresolvedReference then
                        tciLock.AcquireLock(fun tcitok ->
                            RequireTcImportsLock(tcitok, ccuThunks)
                            ccuThunks.Add(ccuThunk, fixupThunk)))
#if !NO_TYPEPROVIDERS
            ccuRawDataAndInfos |> List.iter (fun (_, _, phase2) -> phase2 ())
#endif
            ccuRawDataAndInfos
            |> List.map (p23 >> fun asm -> ResolvedImportedAssembly(asm, m))

        phase2

    // NOTE: When used in the Language Service this can cause the transitive checking of projects. Hence it must be cancellable.
    member tcImports.RegisterAndImportReferencedAssemblies(ctok, nms: AssemblyResolution list) =
        let tryGetAssemblyData (r: AssemblyResolution) =
            async {
                CheckDisposed()
                let m = r.originalReference.Range
                let fileName = r.resolvedPath

                try

                    let! contentsOpt =
                        async {
                            match r.ProjectReference with
                            | Some ilb -> return! ilb.EvaluateRawContents()
                            | None -> return ProjectAssemblyDataResult.Unavailable true
                        }

                    // If we have a project reference but did not get any valid contents,
                    //     just return None and do not attempt to read elsewhere.
                    match contentsOpt with
                    | ProjectAssemblyDataResult.Unavailable false -> return None
                    | _ ->

                        match contentsOpt with
                        | ProjectAssemblyDataResult.Available ilb -> return Some(r, ilb)
                        | ProjectAssemblyDataResult.Unavailable _ ->
                            let ilModule, ilAssemblyRefs = tcImports.OpenILBinaryModule(ctok, fileName, m)
                            return Some(r, RawFSharpAssemblyDataBackedByFileOnDisk(ilModule, ilAssemblyRefs))

                with e ->
                    errorR (Error(FSComp.SR.buildProblemReadingAssembly (fileName, e.Message), m))
                    return None
            }

        let registerDll (r: AssemblyResolution, assemblyData: IRawFSharpAssemblyData) =
            let m = r.originalReference.Range
            let fileName = r.resolvedPath
            let ilShortAssemName = assemblyData.ShortAssemblyName
            let ilScopeRef = assemblyData.ILScopeRef

            if tcImports.IsAlreadyRegistered ilShortAssemName then

                let phase2 () =
                    [ tcImports.FindCcuInfo(ctok, m, ilShortAssemName, lookupOnly = true) ]

                async { return phase2 () }
            else
                let dllinfo =
                    {
                        RawMetadata = assemblyData
                        FileName = fileName
#if !NO_TYPEPROVIDERS
                        ProviderGeneratedAssembly = None
                        IsProviderGenerated = false
                        ProviderGeneratedStaticLinkMap = None
#endif
                        ILScopeRef = ilScopeRef
                        ILAssemblyRefs = assemblyData.ILAssemblyRefs
                    }

                tcImports.RegisterDll dllinfo

                let phase2 =
                    if assemblyData.HasAnyFSharpSignatureDataAttribute then
                        if not assemblyData.HasMatchingFSharpSignatureDataAttribute then
                            errorR (Error(FSComp.SR.buildDifferentVersionMustRecompile fileName, m))
                            tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo)
                        else
                            try
                                tcImports.PrepareToImportReferencedFSharpAssembly(ctok, m, fileName, dllinfo)
                            with e ->
                                error (Error(FSComp.SR.buildErrorOpeningBinaryFile (fileName, e.Message), m))
                    else
                        tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo)

                async { return phase2 () }

        async {
            CheckDisposed()

            let tcConfig = tcConfigP.Get ctok

            let runMethod computations =
                match tcConfig.parallelReferenceResolution with
                | ParallelReferenceResolution.On -> MultipleDiagnosticsLoggers.Parallel computations
                | ParallelReferenceResolution.Off -> MultipleDiagnosticsLoggers.Sequential computations

            let! assemblyData = nms |> List.map tryGetAssemblyData |> runMethod

            // Preserve determinicstic order of references, because types from later assemblies may shadow earlier ones.
            let phase2s = assemblyData |> Seq.choose id |> Seq.map registerDll |> List.ofSeq

            fixupOrphanCcus ()

            let! ccuinfos = phase2s |> runMethod

            if importsBase.IsSome then
                importsBase.Value.CcuTable.Values |> Seq.iter addConstraintSources
                ccuTable.Values |> Seq.iter addConstraintSources

            return ccuinfos |> List.concat
        }

    /// Note that implicit loading is not used for compilations from MSBuild, which passes ``--noframework``
    /// Implicit loading is done in non-cancellation mode. Implicit loading is never used in the language service, so
    /// no cancellation is needed.
    member tcImports.ImplicitLoadIfAllowed(ctok, m, assemblyName, lookupOnly) =
        CheckDisposed()
        // If the user is asking for the default framework then also try to resolve other implicit assemblies as they are discovered.
        // Using this flag to mean 'allow implicit discover of assemblies'.
        let tcConfig = tcConfigP.Get ctok

        if not lookupOnly && tcConfig.implicitlyResolveAssemblies then
            let tryFile speculativeFileName =
                let foundFile =
                    tcImports.TryResolveAssemblyReference(
                        ctok,
                        AssemblyReference(m, speculativeFileName, None),
                        ResolveAssemblyReferenceMode.Speculative
                    )

                match foundFile with
                | OkResult(warns, res) ->
                    ReportWarnings warns

                    tcImports.RegisterAndImportReferencedAssemblies(ctok, res)
                    |> Async.RunSynchronously
                    |> ignore

                    true
                | ErrorResult(_warns, _err) ->
                    // Throw away warnings and errors - this is speculative loading
                    false

            if tryFile (assemblyName + ".dll") then
                ()
            else
                tryFile (assemblyName + ".exe") |> ignore

#if !NO_TYPEPROVIDERS
    member tcImports.TryFindProviderGeneratedAssemblyByName(ctok, assemblyName: string) : Assembly option =
        // The assembly may not be in the resolutions, but may be in the load set including EST injected assemblies
        match tcImports.TryFindDllInfo(ctok, range0, assemblyName, lookupOnly = true) with
        | Some res ->
            // Provider-generated assemblies don't necessarily have an on-disk representation we can load.
            res.ProviderGeneratedAssembly
        | _ -> None
#endif

    /// Only used by F# Interactive
    member _.TryFindExistingFullyQualifiedPathBySimpleAssemblyName simpleAssemName : string option =
        tciLock.AcquireLock(fun tcitok ->
            RequireTcImportsLock(tcitok, resolutions)

            resolutions.TryFindBySimpleAssemblyName simpleAssemName
            |> Option.map (fun r -> r.resolvedPath))

    /// Only used by F# Interactive
    member _.TryFindExistingFullyQualifiedPathByExactAssemblyRef(assemblyRef: ILAssemblyRef) : string option =
        tciLock.AcquireLock(fun tcitok ->
            RequireTcImportsLock(tcitok, resolutions)

            resolutions.TryFindByExactILAssemblyRef assemblyRef
            |> Option.map (fun r -> r.resolvedPath))

    member _.TryResolveAssemblyReference
        (ctok, assemblyReference: AssemblyReference, mode: ResolveAssemblyReferenceMode)
        : OperationResult<AssemblyResolution list> =
        tciLock.AcquireLock(fun tcitok ->
            let tcConfig = tcConfigP.Get ctok

            RequireTcImportsLock(tcitok, resolutions)
            // First try to lookup via the original reference text.
            match resolutions.TryFindByOriginalReference assemblyReference with
            | Some assemblyResolution -> ResultD [ assemblyResolution ]
            | None ->
                // Next try to lookup up by the exact full resolved path.
                match resolutions.TryFindByResolvedPath assemblyReference.Text with
                | Some assemblyResolution -> ResultD [ assemblyResolution ]
                | None ->
                    if tcConfigP.Get(ctok).useSimpleResolution then
                        let action =
                            match mode with
                            | ResolveAssemblyReferenceMode.ReportErrors -> CcuLoadFailureAction.RaiseError
                            | ResolveAssemblyReferenceMode.Speculative -> CcuLoadFailureAction.ReturnNone

                        match tcConfig.ResolveLibWithDirectories(action, assemblyReference) with
                        | Some resolved ->
                            resolutions <- resolutions.AddResolutionResults [ resolved ]
                            ResultD [ resolved ]
                        | None -> ErrorD(AssemblyNotResolved(assemblyReference.Text, assemblyReference.Range))
                    else
                        // This is a previously unencountered assembly. Resolve it and add it to the list.
                        // But don't cache resolution failures because the assembly may appear on the disk later.
                        let resolved, unresolved =
                            TcConfig.TryResolveLibsUsingMSBuildRules(tcConfig, [ assemblyReference ], assemblyReference.Range, mode)

                        match resolved, unresolved with
                        | assemblyResolution :: _, _ ->
                            resolutions <- resolutions.AddResolutionResults resolved
                            ResultD [ assemblyResolution ]
                        | _, _ :: _ ->
                            resolutions <- resolutions.AddUnresolvedReferences unresolved
                            ErrorD(AssemblyNotResolved(assemblyReference.Text, assemblyReference.Range))
                        | [], [] ->
                            // Note, if mode=ResolveAssemblyReferenceMode.Speculative and the resolution failed then TryResolveLibsUsingMSBuildRules returns
                            // the empty list and we convert the failure into an AssemblyNotResolved here.
                            ErrorD(AssemblyNotResolved(assemblyReference.Text, assemblyReference.Range)))

    member tcImports.ResolveAssemblyReference(ctok, assemblyReference, mode) : AssemblyResolution list =
        CommitOperationResult(tcImports.TryResolveAssemblyReference(ctok, assemblyReference, mode))

    // Note: This returns a TcImports object. However, framework TcImports are not currently disposed. The only reason
    // we dispose TcImports is because we need to dispose type providers, and type providers are never included in the framework DLL set.
    // If a framework set ever includes type providers, you will not have to worry about explicitly calling Dispose as the Finalizer will handle it.
    static member BuildFrameworkTcImports(tcConfigP: TcConfigProvider, frameworkDLLs, nonFrameworkDLLs) =
        async {
            let ctok = CompilationThreadToken()
            let tcConfig = tcConfigP.Get ctok

            let tcResolutions =
                TcAssemblyResolutions.BuildFromPriorResolutions(tcConfig, frameworkDLLs, [])

            let tcAltResolutions =
                TcAssemblyResolutions.BuildFromPriorResolutions(tcConfig, nonFrameworkDLLs, [])

            let frameworkTcImports = new TcImports(tcConfigP, tcResolutions, None, None)

            // Fetch the primaryAssembly from the referenced assemblies otherwise
            let primaryAssemblyReference =
                let path =
                    frameworkDLLs
                    |> List.tryFind (fun dll ->
                        let baseName = Path.GetFileNameWithoutExtension(dll.resolvedPath)

                        let res =
                            String.Compare(baseName, tcConfig.primaryAssembly.Name, StringComparison.OrdinalIgnoreCase)

                        res = 0)

                match path with
                | Some p -> AssemblyReference(range0, p.resolvedPath, None)
                | None -> tcConfig.PrimaryAssemblyDllReference()

            let primaryAssemblyResolution =
                frameworkTcImports.ResolveAssemblyReference(ctok, primaryAssemblyReference, ResolveAssemblyReferenceMode.ReportErrors)

            let! primaryAssem = frameworkTcImports.RegisterAndImportReferencedAssemblies(ctok, primaryAssemblyResolution)

            let primaryScopeRef =
                match primaryAssem with
                | [ ResolvedImportedAssembly(ccu, _) ] -> ccu.FSharpViewOfMetadata.ILScopeRef
                | _ -> failwith "primaryScopeRef - unexpected"

            let resolvedAssemblies = tcResolutions.GetAssemblyResolutions()

            let primaryAssemblyResolvedPath =
                match primaryAssemblyResolution with
                | [ primaryAssemblyResolution ] -> primaryAssemblyResolution.resolvedPath
                | _ -> failwith "primaryAssemblyResolvedPath - unexpected"

            let readerSettings: ILReaderOptions =
                {
                    pdbDirPath = None
                    reduceMemoryUsage = tcConfig.reduceMemoryUsage
                    metadataOnly = MetadataOnlyFlag.Yes
                    tryGetMetadataSnapshot = tcConfig.tryGetMetadataSnapshot
                }

            let tryFindEquivPrimaryAssembly (resolvedAssembly: AssemblyResolution) =
                if primaryAssemblyResolvedPath = resolvedAssembly.resolvedPath then
                    None
                else
                    let reader = OpenILModuleReader resolvedAssembly.resolvedPath readerSettings
                    let mdef = reader.ILModuleDef

                    // We check the exported types of all assemblies, since many may forward System.Object,
                    // but only check the actual type definitions for specific assemblies that we know
                    // might actually declare System.Object.
                    match mdef.Manifest with
                    | Some manifest when
                        manifest.ExportedTypes.TryFindByName "System.Object" |> Option.isSome
                        || PrimaryAssembly.IsPossiblePrimaryAssembly resolvedAssembly.resolvedPath
                           && mdef.TypeDefs.ExistsByName "System.Object"
                        ->
                        mkRefToILAssembly manifest |> Some
                    | _ -> None

            // Find assemblies which also declare System.Object
            let equivPrimaryAssemblyRefs =
                resolvedAssemblies |> List.choose tryFindEquivPrimaryAssembly

            let! fslibCcu, fsharpCoreAssemblyScopeRef =
                async {
                    if tcConfig.compilingFSharpCore then
                        // When compiling FSharp.Core.dll, the fslibCcu reference to FSharp.Core.dll is a delayed ccu thunk fixed up during type checking
                        return CcuThunk.CreateDelayed getFSharpCoreLibraryName, ILScopeRef.Local
                    else
                        let coreLibraryReference = tcConfig.CoreLibraryDllReference()

                        let resolvedAssemblyRef =
                            match tcResolutions.TryFindByOriginalReference coreLibraryReference with
                            | Some resolution -> Some resolution
                            | _ ->
                                // Are we using a "non-canonical" FSharp.Core?
                                match tcAltResolutions.TryFindByOriginalReference coreLibraryReference with
                                | Some resolution -> Some resolution
                                | _ -> tcResolutions.TryFindByOriginalReferenceText getFSharpCoreLibraryName // was the ".dll" elided?

                        match resolvedAssemblyRef with
                        | Some coreLibraryResolution ->
                            match! frameworkTcImports.RegisterAndImportReferencedAssemblies(ctok, [ coreLibraryResolution ]) with
                            | [ ResolvedImportedAssembly(fslibCcuInfo, _) ] ->
                                return fslibCcuInfo.FSharpViewOfMetadata, fslibCcuInfo.ILScopeRef
                            | _ ->
                                return
                                    error (
                                        InternalError(
                                            $"no import of {coreLibraryResolution.resolvedPath}",
                                            coreLibraryResolution.originalReference.Range
                                        )
                                    )
                        | None -> return error (InternalError($"no resolution of '{coreLibraryReference.Text}'", rangeStartup))
                }

            // Load the rest of the framework DLLs all at once (they may be mutually recursive)
            let! _assemblies = frameworkTcImports.RegisterAndImportReferencedAssemblies(ctok, resolvedAssemblies)

            // These are the DLLs we can search for well-known types
            let sysCcus =
                [|
                    for ccu in frameworkTcImports.GetCcusInDeclOrder() do
                        ccu
                |]

            let tryFindSysTypeCcu path typeName publicOnly =
                sysCcus |> Array.tryFind (fun ccu -> ccuHasType ccu path typeName publicOnly)

            let ilGlobals =
                mkILGlobals (primaryScopeRef, equivPrimaryAssemblyRefs, fsharpCoreAssemblyScopeRef)

            // OK, now we have both mscorlib.dll and FSharp.Core.dll we can create TcGlobals
            let tcGlobals =
                TcGlobals(
                    tcConfig.compilingFSharpCore,
                    ilGlobals,
                    fslibCcu,
                    tcConfig.implicitIncludeDir,
                    tcConfig.mlCompatibility,
                    tcConfig.isInteractive,
                    tcConfig.checkNullness,
                    tcConfig.useReflectionFreeCodeGen,
                    tryFindSysTypeCcu,
                    tcConfig.emitDebugInfoInQuotations,
                    tcConfig.noDebugAttributes,
                    tcConfig.pathMap,
                    tcConfig.langVersion,
                    tcConfig.realsig,
                    tcConfig.compilationMode
                )

#if DEBUG
            // the global_g reference cell is used only for debug printing
            global_g <- Some tcGlobals
#endif
            frameworkTcImports.SetTcGlobals tcGlobals
            return tcGlobals, frameworkTcImports
        }

    member _.ReportUnresolvedAssemblyReferences knownUnresolved =
        // Report that an assembly was not resolved.
        let reportAssemblyNotResolved (file, originalReferences: AssemblyReference list) =
            originalReferences
            |> List.iter (fun originalReference -> errorR (AssemblyNotResolved(file, originalReference.Range)))

        knownUnresolved
        |> List.map (function
            | UnresolvedAssemblyReference(file, originalReferences) -> file, originalReferences)
        |> List.iter reportAssemblyNotResolved

    static member BuildNonFrameworkTcImports
        (tcConfigP: TcConfigProvider, baseTcImports, nonFrameworkReferences, knownUnresolved, dependencyProvider)
        =

        async {
            let ctok = CompilationThreadToken()
            let tcConfig = tcConfigP.Get ctok

            let tcResolutions =
                TcAssemblyResolutions.BuildFromPriorResolutions(tcConfig, nonFrameworkReferences, knownUnresolved)

            let references = tcResolutions.GetAssemblyResolutions()

            let tcImports =
                new TcImports(tcConfigP, tcResolutions, Some baseTcImports, Some dependencyProvider)

            let! _assemblies = tcImports.RegisterAndImportReferencedAssemblies(ctok, references)
            tcImports.ReportUnresolvedAssemblyReferences knownUnresolved
            return tcImports
        }

    static member BuildTcImports(tcConfigP: TcConfigProvider, dependencyProvider) =
        async {
            let ctok = CompilationThreadToken()
            let tcConfig = tcConfigP.Get ctok

            let frameworkDLLs, nonFrameworkReferences, knownUnresolved =
                TcAssemblyResolutions.SplitNonFoundationalResolutions(tcConfig)

            let! tcGlobals, frameworkTcImports = TcImports.BuildFrameworkTcImports(tcConfigP, frameworkDLLs, nonFrameworkReferences)

            let! tcImports =
                TcImports.BuildNonFrameworkTcImports(
                    tcConfigP,
                    frameworkTcImports,
                    nonFrameworkReferences,
                    knownUnresolved,
                    dependencyProvider
                )

            return tcGlobals, tcImports
        }

    interface IDisposable with
        member _.Dispose() = dispose ()

    override tcImports.ToString() = "TcImports(...)"

/// Process #r in F# Interactive.
/// Adds the reference to the tcImports and add the ccu to the type checking environment.
let RequireReferences (ctok, tcImports: TcImports, tcEnv, thisAssemblyName, resolutions) =

    let ccuinfos =
        tcImports.RegisterAndImportReferencedAssemblies(ctok, resolutions)
        |> Async.RunSynchronously

    let asms =
        ccuinfos
        |> List.map (function
            | ResolvedImportedAssembly(asm, m) -> asm, m
            | UnresolvedImportedAssembly(assemblyName, m) -> error (Error(FSComp.SR.buildCouldNotResolveAssembly assemblyName, m)))

    let g = tcImports.GetTcGlobals()
    let amap = tcImports.GetImportMap()

    let _openDecls, tcEnv =
        (tcEnv, asms)
        ||> List.collectFold (fun tcEnv (asm, m) ->
            AddCcuToTcEnv(
                g,
                amap,
                m,
                tcEnv,
                thisAssemblyName,
                asm.FSharpViewOfMetadata,
                asm.AssemblyAutoOpenAttributes,
                asm.AssemblyInternalsVisibleToAttributes
            ))

    let asms = asms |> List.map fst

    tcEnv, asms

#endif //!FABLE_COMPILER
